From 98e063869b928830229c863ca150dba260996d00 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Sat, 2 Feb 2019 17:39:48 +0800 Subject: [PATCH 01/54] =?UTF-8?q?[2019.2.2]=E6=8F=90=E4=BA=A4=E4=B8=80?= =?UTF-8?q?=E4=BA=9Bvideo=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 3 +- app/src/main/cpp/camera/CameraFilter.h | 1 + .../com/cangwang/magic/camera/CameraCompat.kt | 14 ++- .../cangwang/magic/camera/CameraCompatV19.kt | 4 +- .../cangwang/magic/camera/CameraCompatV21.kt | 18 +++- .../com/cangwang/magic/util/OpenGLJniLib.kt | 1 + .../view/CameraFilterSurfaceCallbackV2.kt | 91 ++++++++++++++++++- 7 files changed, 124 insertions(+), 8 deletions(-) diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index 269f35e..811d68d 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -204,4 +204,5 @@ bool CameraFilter::savePhoto(std::string saveFileAddress){ return filter->savePhoto(saveFileAddress); } return false; -} \ No newline at end of file +} + diff --git a/app/src/main/cpp/camera/CameraFilter.h b/app/src/main/cpp/camera/CameraFilter.h index 018057c..90c50a4 100644 --- a/app/src/main/cpp/camera/CameraFilter.h +++ b/app/src/main/cpp/camera/CameraFilter.h @@ -28,6 +28,7 @@ class CameraFilter:public GLBase{ void setFilter(int type); void setBeautyLevel(int level); bool savePhoto(std::string saveFileAddress); + bool changeVideoEgl(); protected: diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt index 6b815f6..3d4d478 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt @@ -85,7 +85,7 @@ abstract class CameraCompat(protected var mContext: Context) { * 这里会两次调用此函数,第一次会初始化走openCamera, * 第二次调用才能正常开始预览 */ - fun startPreview() { + fun startPreview(callBack: CameraStateCallBack?=null) { // if (Looper.myLooper() != Looper.getMainLooper()) { // throw RuntimeException("you must start camera preview in main thread") // } @@ -103,10 +103,10 @@ abstract class CameraCompat(protected var mContext: Context) { return } mStarted = true - onStartPreview() + onStartPreview(callBack) } - protected abstract fun onStartPreview() + protected abstract fun onStartPreview(callBack: CameraStateCallBack?=null) fun stopPreview(releaseSurface: Boolean) { if (!mStarted) { @@ -152,6 +152,10 @@ abstract class CameraCompat(protected var mContext: Context) { mSwitchFlag = false } + fun getCameraType():Int{ + return if (mCameraType == FRONT_CAMERA) BACK_CAMERA else FRONT_CAMERA + } + class CameraSize { var width: Int = 0 var height: Int = 0 @@ -187,4 +191,8 @@ abstract class CameraCompat(protected var mContext: Context) { } } + interface CameraStateCallBack{ + fun onConfigured() + fun onConfigureFailed() + } } diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV19.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV19.kt index 8097e46..ab73540 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV19.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV19.kt @@ -24,12 +24,14 @@ class CameraCompatV19(context: Context) : CameraCompat(context) { } } - override fun onStartPreview() { + override fun onStartPreview(callBack: CameraStateCallBack?) { try { mCamera?.setPreviewTexture(mSurfaceTexture) mCamera?.startPreview() + callBack?.onConfigured() } catch (e: Throwable) { Log.e(TAG, e.toString()) + callBack?.onConfigureFailed() } } diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt index 241cfca..3ba7de2 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt @@ -129,7 +129,7 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { } - override fun onStartPreview() { + override fun onStartPreview(callBack: CameraStateCallBack?) { try { mSurface = Surface(mSurfaceTexture) outputSize?.let { @@ -147,7 +147,21 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { CaptureRequest.FLASH_MODE_OFF) mCamera?.createCaptureSession(listOf(it), - mCaptureStateCallback, mBackgroundHandler) + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(session: CameraCaptureSession) { + if (mCamera == null) { + return + } + mCaptureSession = session + startRequest(session) + callBack?.onConfigured() + } + + override fun onConfigureFailed(session: CameraCaptureSession) { + Log.e(TAG, "onConfigureFailed") + callBack?.onConfigureFailed() + } + }, mBackgroundHandler) } } catch (e: Throwable) { diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index 93d9700..46a6fa5 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -51,6 +51,7 @@ object OpenGLJniLib{ external fun magicFilterRelease() +// external fun magicFilterchangeVideoEgl(surface: Surface) /** * 图片滤镜创建 diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt index 53a6ff9..587d6b1 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt @@ -3,6 +3,7 @@ package com.cangwang.magic.view import android.annotation.SuppressLint import android.graphics.SurfaceTexture +import android.media.MediaRecorder import android.os.Build import android.os.Environment import android.util.Log @@ -19,6 +20,7 @@ import io.reactivex.schedulers.Schedulers import java.io.IOException import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean /** * Created by zjl on 2018/10/12. @@ -34,6 +36,10 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback private var height = 0 private var isTakePhoto = false + private var mMediaRecorder:MediaRecorder?=null + private var isRecordVideo = AtomicBoolean() + private var previewSurface:Surface?=null + override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width this.height = height @@ -47,6 +53,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback override fun surfaceCreated(holder: SurfaceHolder?) { holder?.let { + previewSurface = it.surface initOpenGL(it.surface) } } @@ -73,6 +80,78 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback } } + fun startVideoRecord(path:String):Boolean{ + if (isRecordVideo.get()){ + Log.e(TAG,"video is recording") + return false + } + if (path.isEmpty()){ + Log.e(TAG,"record path is empty") + return false + } + if (!setMediaRecordParam(path)){ + Log.e(TAG,"record path is empty") + return false + } + startRecordVideo() + return true + } + + fun setMediaRecordParam(path:String):Boolean{ + mMediaRecorder = MediaRecorder() + mMediaRecorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) + setVideoSource(MediaRecorder.VideoSource.SURFACE) + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + setOutputFile(path) + val bitRate= width*height + setVideoEncodingBitRate(bitRate) + setVideoSize(width,height) + setVideoEncoder(MediaRecorder.VideoEncoder.H264) + + setAudioEncodingBitRate(8000) + setAudioChannels(1) + setAudioSamplingRate(8000) + setAudioEncodingBitRate(MediaRecorder.AudioEncoder.AAC) + if (mCamera?.getCameraType() == CameraCompat.BACK_CAMERA){ + setOrientationHint(90) + }else{ + setOrientationHint(270) + } + try { + prepare() + }catch (e:IOException){ + e.printStackTrace() + return false + } + } + return true + } + + fun startRecordVideo(){ + isRecordVideo.set(true) + mMediaRecorder?.surface?.let { + initOpenGL(it) + } + } + + fun stopRecordVideo(){ + if(isRecordVideo.get()){ + mMediaRecorder?.stop() + } + } + + fun relaseRecordVideo(){ + mMediaRecorder?.apply { + stop() + reset() + release() + } + if (isRecordVideo.get()){ + isRecordVideo.set(false) + } + } + fun changeCamera(camera:CameraCompat? ){ mExecutor.execute { mCamera = camera @@ -128,7 +207,17 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback } fun doStartPreview(){ - mCamera?.startPreview() + mCamera?.startPreview(object :CameraCompat.CameraStateCallBack{ + override fun onConfigured() { + if (isRecordVideo.get()){ + mMediaRecorder?.start() + } + } + + override fun onConfigureFailed() { + + } + }) } @SuppressLint("CheckResult") From 95f8393e88c79abd94727c9f186718c18ffb8745 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 21 Feb 2019 20:11:31 +0800 Subject: [PATCH 02/54] =?UTF-8?q?[2019.2.21]=E6=B7=BB=E5=8A=A0=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E6=B1=A0=E9=80=BB=E8=BE=91=E5=92=8C=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E6=B1=A0=E5=87=BD=E6=95=B0=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 7 +- app/src/main/cpp/camera/CameraFilter.h | 1 + .../cpp/filter/gpuimage/GpuImageFilter.cpp | 30 ++++-- .../main/cpp/filter/gpuimage/GpuImageFilter.h | 5 +- app/src/main/cpp/image/ImageFilter.cpp | 7 +- app/src/main/cpp/image/ImageFilter.h | 1 + app/src/main/cpp/utils/MagicThreadPool.h | 96 +++++++++++++++++++ 7 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 app/src/main/cpp/utils/MagicThreadPool.h diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index 811d68d..42667c9 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -54,7 +54,8 @@ CameraFilter::CameraFilter(ANativeWindow *window): mWindow(window),mEGLCore(new CameraFilter::CameraFilter(ANativeWindow *window,AAssetManager* assetManager): mWindow(window),mEGLCore(new EGLCore()), mAssetManager(assetManager),mTextureId(0),mTextureLoc(0), - mMatrixLoc(0),filter(nullptr),cameraInputFilter(nullptr){ + mMatrixLoc(0),filter(nullptr),cameraInputFilter(nullptr), + pool(new std::MagicThreadPool()){ //清空mMatrix数组 memset(mMatrix,0, sizeof(mMatrix)); mMatrix[0] = 1; @@ -94,6 +95,8 @@ CameraFilter::~CameraFilter() { } mAssetManager = nullptr; + //释放线程池 + free(pool); } void CameraFilter::setFilter(AAssetManager* assetManager) { @@ -105,6 +108,7 @@ void CameraFilter::setFilter(AAssetManager* assetManager) { } // filter = new MagicAmaroFilter(assetManager); filter = new MagicNoneFilter(assetManager); + filter->setPool(pool); ALOGD("set filter success"); } @@ -187,6 +191,7 @@ void CameraFilter::setFilter(GPUImageFilter* gpuImageFilter) { filter= nullptr; } filter = gpuImageFilter; + filter->setPool(pool); ALOGD("set filter success"); if (filter!= nullptr) filter->init(); diff --git a/app/src/main/cpp/camera/CameraFilter.h b/app/src/main/cpp/camera/CameraFilter.h index 90c50a4..8329ec2 100644 --- a/app/src/main/cpp/camera/CameraFilter.h +++ b/app/src/main/cpp/camera/CameraFilter.h @@ -42,5 +42,6 @@ class CameraFilter:public GLBase{ GLint mMatrixLoc; GLfloat mMatrix[16]; EGLCore *mEGLCore; + std::MagicThreadPool *pool; }; diff --git a/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp b/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp index 8483e0a..9a81371 100644 --- a/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp +++ b/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp @@ -4,7 +4,6 @@ #include #define STB_IMAGE_WRITE_IMPLEMENTATION #include "src/main/cpp/utils/stb_image_write.h" - #include "src/main/cpp/utils/TextureRotationUtil.h" #define LOG_TAG "GPUImageFilter" @@ -33,7 +32,8 @@ GPUImageFilter::GPUImageFilter(AAssetManager *assetManager,std::string *vertexSh mAssetManager(assetManager), mGLCubeBuffer(getCube()), mGLTextureBuffer(getRotation(NORMAL, false, false)), - mScreenWidth(0),mScreenHeight(0),mDisplayWidth(0),mDisplayHeight(0){ + mScreenWidth(0),mScreenHeight(0),mDisplayWidth(0),mDisplayHeight(0), + pool(nullptr){ memcpy(mvpMatrix,NONE_MATRIX,16); } @@ -43,6 +43,7 @@ GPUImageFilter::~GPUImageFilter() { mGLTextureBuffer = nullptr; mAssetManager= nullptr; mvpMatrix = nullptr; + pool = nullptr; } void GPUImageFilter::init() { @@ -321,8 +322,12 @@ void GPUImageFilter::savePictureInThread() { //获取帧内字节 glReadPixels(0, 0, mDisplayWidth, mDisplayHeight, GL_RGBA, GL_UNSIGNED_BYTE, data); //使用线程保存图片 - std::thread thread = std::thread(std::bind(&GPUImageFilter::savePicture, this,savePhotoAddress, data, mDisplayWidth,mDisplayHeight,1)); - thread.detach(); + try { + if(pool!= nullptr) + pool->commit(GPUImageFilter::savePicture,savePhotoAddress, data, mDisplayWidth,mDisplayHeight,1); + }catch (std::exception& e){ + ALOGE("saveImageInThread some unhappy happed,error = %s",e.what()); + } } } @@ -348,8 +353,17 @@ void GPUImageFilter::saveImageInThread(std::string saveFileAddress){ // glReadPixels(0, 0, mDisplayWidth, mDisplayHeight, GL_RGBA, GL_UNSIGNED_BYTE, data); // //使用线程保存图片 // std::thread thread = std::thread(std::bind(&GPUImageFilter::savePicture, this,saveFileAddress, data, mDisplayWidth,mDisplayHeight,0)); - std::thread thread = std::thread(std::bind(&GPUImageFilter::savePicture, this,saveFileAddress, mPhoData, mDisplayWidth,mDisplayHeight,0)); - thread.detach(); +// std::thread thread = std::thread(std::bind(&GPUImageFilter::savePicture, this,saveFileAddress, mPhoData, mDisplayWidth,mDisplayHeight,0)); +// thread.detach(); + try { + if(pool!= nullptr) + pool->commit(GPUImageFilter::savePicture,saveFileAddress, mPhoData, mDisplayWidth,mDisplayHeight,0); +// pool->commit([=,self=this](){ +// self->savePicture(saveFileAddress, mPhoData, mDisplayWidth,mDisplayHeight,0); +// }); + }catch (std::exception& e){ + ALOGE("saveImageInThread some unhappy happed,error = %s",e.what()); + } } } @@ -370,6 +384,10 @@ bool GPUImageFilter::savePicture(std::string saveFileAddress,unsigned char* data }; } +void GPUImageFilter::setPool(std::MagicThreadPool *pool) { + this->pool = pool; +} + void GPUImageFilter::destroy() { mIsInitialized = false; glDeleteProgram(mGLProgId); diff --git a/app/src/main/cpp/filter/gpuimage/GpuImageFilter.h b/app/src/main/cpp/filter/gpuimage/GpuImageFilter.h index eaf557f..1ead5b6 100644 --- a/app/src/main/cpp/filter/gpuimage/GpuImageFilter.h +++ b/app/src/main/cpp/filter/gpuimage/GpuImageFilter.h @@ -8,6 +8,7 @@ #include #include #include +#include #ifndef _GPUImageFilter #define _GPUImageFilter @@ -35,7 +36,7 @@ class GPUImageFilter { virtual void onDrawArraysAfter() {} bool savePhoto(std::string directory); - bool savePicture(std::string saveFileAddress,unsigned char* data,int width,int height,int type); + static bool savePicture(std::string saveFileAddress,unsigned char* data,int width,int height,int type); void savePictureInThread(); void saveImageInThread(std::string saveFileAddress); void setOrientation(int degree); @@ -45,6 +46,7 @@ class GPUImageFilter { void initPixelBuffer(int width, int height); void destroyPixelBuffers(); void drawPixelBuffer(); + void setPool(std::MagicThreadPool* pool); AAssetManager* mAssetManager; int mScreenWidth; @@ -86,6 +88,7 @@ class GPUImageFilter { GLuint mPixelBuffer; long mPhoSize; unsigned char* mPhoData; + std::MagicThreadPool *pool; }; #endif \ No newline at end of file diff --git a/app/src/main/cpp/image/ImageFilter.cpp b/app/src/main/cpp/image/ImageFilter.cpp index f3ba8a0..36daa27 100644 --- a/app/src/main/cpp/image/ImageFilter.cpp +++ b/app/src/main/cpp/image/ImageFilter.cpp @@ -30,7 +30,8 @@ ImageFilter::ImageFilter(ANativeWindow *window,AAssetManager* assetManager,std:: mAssetManager(assetManager),mTextureId(0),mTextureLoc(0), mMatrixLoc(0),filter(nullptr), imageInput(new ImageInput(assetManager,path)),beautyFilter(nullptr),imgPath(path),degree(degree), - mvpMatrix(new float[16]){ + mvpMatrix(new float[16]), + pool(new std::MagicThreadPool()){ //清空mMatrix数组 memset(mMatrix,0, sizeof(mMatrix)); mMatrix[0] = 1; @@ -67,6 +68,8 @@ ImageFilter::~ImageFilter() { } mAssetManager = nullptr; + //释放线程池 + free(pool); } void ImageFilter::setFilter(AAssetManager* assetManager) { @@ -74,6 +77,7 @@ void ImageFilter::setFilter(AAssetManager* assetManager) { filter->destroy(); } filter = new MagicNoneFilter(assetManager); + filter->setPool(pool); //调整滤镜中的图片的方向问题 filter->setOrientation(degree); ALOGD("set filter success"); @@ -203,6 +207,7 @@ void ImageFilter::setFilter(GPUImageFilter* gpuImageFilter) { filter= nullptr; } filter = gpuImageFilter; + filter->setPool(pool); ALOGD("set filter success"); if (filter!= nullptr) filter->init(); diff --git a/app/src/main/cpp/image/ImageFilter.h b/app/src/main/cpp/image/ImageFilter.h index 706da68..990fc0b 100644 --- a/app/src/main/cpp/image/ImageFilter.h +++ b/app/src/main/cpp/image/ImageFilter.h @@ -51,5 +51,6 @@ class ImageFilter:public GPUImageFilter{ int mFrameWidth = -1; int mFrameHeight = -1; float *mvpMatrix; + std::MagicThreadPool *pool; }; diff --git a/app/src/main/cpp/utils/MagicThreadPool.h b/app/src/main/cpp/utils/MagicThreadPool.h new file mode 100644 index 0000000..92a3004 --- /dev/null +++ b/app/src/main/cpp/utils/MagicThreadPool.h @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +namespace std { +#define MAX_THREAD_NUM = 256 + + class MagicThreadPool { + public: + inline MagicThreadPool(unsigned short size = 4) : stoped{false} { + idlThrNum = size < 1 ? 1 : size; + for (size = 0; size < idlThrNum; ++size) { //初始化线程数量 + pool.emplace_back( + [this] { // 工作线程函数 + while (!this->stoped) { + std::function task; + { // 获取一个待执行的 task + std::unique_lock lock{ + this->m_lock};// unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock() + this->cv_task.wait(lock, + [this] { + return this->stoped.load() || + !this->tasks.empty(); + } + ); // wait 直到有 task + if (this->stoped && this->tasks.empty()) + return false; + task = std::move(this->tasks.front()); // 取一个 task + this->tasks.pop(); + } + idlThrNum--; + task(); + idlThrNum++; + } + } + ); + } + } + + inline ~MagicThreadPool() { + stoped.store(true); + cv_task.notify_all(); // 唤醒所有线程执行 + for (std::thread &thread : pool) { + //thread.detach(); // 让线程“自生自灭” + if (thread.joinable()) + thread.join(); // 等待任务结束, 前提:线程一定会执行完 + } + } + + template + auto commit(F &&f, Args &&... args) -> std::future { + if (stoped.load()) // stop == true ?? + throw std::runtime_error("commit on ThreadPool is stopped."); + + using RetType = decltype(f( + args...)); // typename std::result_of::type, 函数 f 的返回值类型 + auto task = std::make_shared >( + std::bind(std::forward(f), std::forward(args)...) + ); // wtf ! + std::future future = task->get_future(); + { // 添加任务到队列 + std::lock_guard lock{ + m_lock};//对当前块的语句加锁 lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock() + tasks.emplace( + [task]() { // push(Task{...}) + (*task)(); + } + ); + } + cv_task.notify_one(); // 唤醒一个线程执行 + + return future; + }; + + //空闲线程数量 + int idlCount() { return idlThrNum; } + + private: + using Task = std::function; + std::vector pool; + std::queue tasks; + std::mutex m_lock; + std::condition_variable cv_task; + std::atomic stoped; + std::atomic idlThrNum; + }; +} +#endif \ No newline at end of file From 8ddd8abeba8f60e33e678646e4ffca99b042d11a Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Fri, 22 Feb 2019 17:51:16 +0800 Subject: [PATCH 03/54] =?UTF-8?q?[2019.2.22]=E6=8F=90=E4=BA=A4=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=BD=95=E5=88=B6=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/egl/EGLCore.cpp | 16 ++- app/src/main/cpp/egl/EGLCore.h | 8 +- .../cangwang/magic/CameraFilterV2Activity.kt | 20 ++- .../com/cangwang/magic/camera/CameraCompat.kt | 5 +- .../cangwang/magic/camera/CameraCompatV21.kt | 8 +- .../cangwang/magic/video/VideoEncoderCoder.kt | 130 ++++++++++++++++++ .../view/CameraFilterSurfaceCallbackV2.kt | 119 +++++++--------- 7 files changed, 228 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index 8da7f39..f4737c5 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -1,7 +1,6 @@ #include #include "EGLCore.h" #include -#include #define LOG_TAG "EGLCore" @@ -93,10 +92,25 @@ GLboolean EGLCore::buildContext(ANativeWindow *window) { return GL_FALSE; } + // 获取eglPresentationTimeANDROID方法的地址 + eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROIDPROC) + eglGetProcAddress("eglPresentationTimeANDROID"); + if (!eglPresentationTimeANDROID) { + ALOGE("eglPresentationTimeANDROID is not available!"); + } + ALOGD("buildContext Succeed"); return GL_TRUE; } +/** + * 设置显示时间戳pts + * @param nsecs + */ +void EGLCore::setPresentationTime(long nsecs) { + eglPresentationTimeANDROID(mDisplay,mSurface,nsecs); +} + /** * 现在只使用单缓冲绘制 */ diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index 70272d5..286acaa 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -1,4 +1,6 @@ #include +#include +#include #include #ifndef _EGLCore @@ -7,7 +9,8 @@ /** * cangwang 2018.12.1 */ -class EGLCore{ +typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay display, EGLSurface surface, khronos_stime_nanoseconds_t time); + class EGLCore{ public: EGLCore(); @@ -15,6 +18,8 @@ class EGLCore{ GLboolean buildContext(ANativeWindow *window); + void setPresentationTime(long nsecs); + void swapBuffer(); void release(); @@ -25,6 +30,7 @@ class EGLCore{ EGLDisplay mDisplay; EGLSurface mSurface; EGLContext mContext; + EGL_PRESENTATION_TIME_ANDROIDPROC eglPresentationTimeANDROID = NULL; }; #endif diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index f0c0e31..ac4d9bc 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -65,7 +65,11 @@ class CameraFilterV2Activity:AppCompatActivity(){ } btn_camera_shutter.setOnClickListener { - takePhoto() + if(mode == MODE_PIC) { + takePhoto() + }else if (mode == MODE_VIDEO){ + takeVideo() + } } btn_camera_switch.setOnClickListener { @@ -73,7 +77,13 @@ class CameraFilterV2Activity:AppCompatActivity(){ } btn_camera_mode.setOnClickListener { - + if(mode == MODE_PIC){ + mode = MODE_VIDEO + btn_camera_mode.setImageResource(R.drawable.icon_camera) + }else if (mode == MODE_VIDEO){ + mode = MODE_PIC + btn_camera_mode.setImageResource(R.drawable.icon_video) + } } btn_camera_beauty.setOnClickListener { @@ -122,7 +132,11 @@ class CameraFilterV2Activity:AppCompatActivity(){ } fun takeVideo(){ - + if(mSurfaceCallback?.isRecording() == true) { + mSurfaceCallback?.releaseRecordVideo() + }else{ + mSurfaceCallback?.startRecordVideo() + } } private fun showFilters() { diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt index 3d4d478..3037369 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt @@ -10,6 +10,7 @@ import android.os.Looper import android.support.annotation.IntDef import android.util.Size import android.util.SparseArray +import android.view.Surface import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @@ -18,6 +19,7 @@ abstract class CameraCompat(protected var mContext: Context) { private var mSwitchFlag: Boolean = false protected var mSurfaceTexture: SurfaceTexture? = null + var surface:Surface?=null /** * 是否支持闪光灯 */ @@ -59,8 +61,9 @@ abstract class CameraCompat(protected var mContext: Context) { initCameraInfo() } - fun setSurfaceTexture(texture: SurfaceTexture) { + fun setSurfaceTexture(texture: SurfaceTexture,surface: Surface?=null) { this.mSurfaceTexture = texture + this.surface =surface } protected fun setFrontCameraId(id: String) { diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt index 3ba7de2..a96b29e 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt @@ -131,7 +131,13 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { override fun onStartPreview(callBack: CameraStateCallBack?) { try { - mSurface = Surface(mSurfaceTexture) + mCaptureSession?.close() + + mSurface = if(surface==null) { + Surface(mSurfaceTexture) + }else{ + surface + } outputSize?.let { mSurfaceTexture?.setDefaultBufferSize(it.width, it.height) } diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt new file mode 100644 index 0000000..34393f3 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -0,0 +1,130 @@ +package com.cangwang.magic.video + +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaFormat +import android.media.MediaMuxer +import android.util.Log +import android.view.Surface +import java.io.File +import java.lang.RuntimeException + +class VideoEncoderCoder{ + companion object { + const val TAG = "VideoEncoderCoder" + const val MINE_TYPE = "video/avc" + const val FRAME_RATE = 30 + const val IFRAME_INTERVAL = 5 + } + + private lateinit var mInputSurface:Surface + private lateinit var mMuxer:MediaMuxer + private lateinit var mEncoder:MediaCodec + private var mBufferInfo:MediaCodec.BufferInfo = MediaCodec.BufferInfo() + private var mTrackIndex = -1 + private var mMuxerStarted = false + + constructor(inputSurface:Surface,width:Int,height:Int,bitRate:Int,outFile:File){ + val format = MediaFormat.createVideoFormat(MINE_TYPE,width,height) + format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) + format.setInteger(MediaFormat.KEY_BIT_RATE,bitRate) + format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE) + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL) + Log.d(TAG, "format: $format") + + mEncoder = MediaCodec.createEncoderByType(MINE_TYPE) + mEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) + mInputSurface = inputSurface + + mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) + mTrackIndex = -1 + mMuxerStarted = false + } + + fun start(){ + mEncoder.start() + } + + fun stop(){ + mEncoder.stop() + } + + fun resume(){ + + } + + fun getInputSurface():Surface{ + return mInputSurface + } + + fun release(){ + Log.d(TAG,"release encoder objects") + mEncoder.stop() + mEncoder.release() + + mMuxer.stop() + mMuxer.release() + } + + fun drainEncoder(endOfStream:Boolean){ + val TIMEOUT_USEC = 10000L + Log.d(TAG,"drainEncoder($endOfStream)") + if (endOfStream){ + Log.d(TAG,"sending EOS to encoder") + mEncoder.signalEndOfInputStream() + } + + var encoderOutputBuffers = mEncoder.outputBuffers + while (true){ + val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo,TIMEOUT_USEC) + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER){ + if (!endOfStream){ + break + }else{ + Log.d(TAG, "no output available, spinning to await EOS") + } + }else if(encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){ + encoderOutputBuffers = mEncoder.outputBuffers + }else if(encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ + if (mMuxerStarted){ + throw RuntimeException("format changed twice") + } + val newFormat = mEncoder.outputFormat + Log.d(TAG,"encoder output format changed: $newFormat") + mTrackIndex = mMuxer.addTrack(newFormat) + mMuxer.start() + mMuxerStarted = true + }else if (encoderStatus <0){ + Log.e(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") + }else{ + val encodedData = encoderOutputBuffers[encoderStatus] ?: throw RuntimeException(("encoderOutputBuffer " + encoderStatus + + " was null")) + if ((mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){ + Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") + mBufferInfo.size = 0 + } + + if (mBufferInfo.size!=0){ + if (!mMuxerStarted) { + throw RuntimeException("muxer hasn't started") + } + encodedData.position(mBufferInfo.offset) + encodedData.limit(mBufferInfo.offset+mBufferInfo.size) + mMuxer.writeSampleData(mTrackIndex,encodedData,mBufferInfo) + Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + + mBufferInfo.presentationTimeUs) + } + mEncoder.releaseOutputBuffer(encoderStatus,false) + + if ((mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ + if (!endOfStream){ + Log.e(TAG, "reached end of stream unexpectedly") + }else{ + Log.d(TAG, "end of stream reached") + } + break + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt index 262e3a8..9a08f5a 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt @@ -13,10 +13,12 @@ import android.widget.Toast import com.cangwang.magic.BaseApplication import com.cangwang.magic.camera.CameraCompat import com.cangwang.magic.util.OpenGLJniLib +import com.cangwang.magic.video.VideoEncoderCoder import io.reactivex.Observable import io.reactivex.ObservableOnSubscribe import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import java.io.File import java.io.IOException import java.util.concurrent.Executors @@ -30,6 +32,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback private val TAG= CameraFilterSurfaceCallbackV2::class.java.simpleName!! private var mSurfaceTexture:SurfaceTexture?=null + private var mSurface:Surface?=null private var mCamera=camera private val mMatrix = FloatArray(16) private var width = 0 @@ -39,6 +42,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback private var mMediaRecorder:MediaRecorder?=null private var isRecordVideo = AtomicBoolean() private var previewSurface:Surface?=null + private var videoEncoder:VideoEncoderCoder ?=null override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width @@ -68,9 +72,10 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback } mSurfaceTexture = SurfaceTexture(textureId) mSurfaceTexture?.setOnFrameAvailableListener { drawOpenGL() } + mSurface = Surface(mSurfaceTexture) try { mSurfaceTexture?.let { - mCamera?.setSurfaceTexture(it) + mCamera?.setSurfaceTexture(it,mSurface) } doStartPreview() }catch (e:IOException){ @@ -80,75 +85,38 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback } } - fun startVideoRecord(path:String):Boolean{ + fun startRecordVideo(){ if (isRecordVideo.get()){ - Log.e(TAG,"video is recording") - return false - } - if (path.isEmpty()){ - Log.e(TAG,"record path is empty") - return false - } - if (!setMediaRecordParam(path)){ - Log.e(TAG,"record path is empty") - return false - } - startRecordVideo() - return true - } - - fun setMediaRecordParam(path:String):Boolean{ - mMediaRecorder = MediaRecorder() - mMediaRecorder?.apply { - setAudioSource(MediaRecorder.AudioSource.MIC) - setVideoSource(MediaRecorder.VideoSource.SURFACE) - setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) - setOutputFile(path) - val bitRate= width*height - setVideoEncodingBitRate(bitRate) - setVideoSize(width,height) - setVideoEncoder(MediaRecorder.VideoEncoder.H264) - - setAudioEncodingBitRate(8000) - setAudioChannels(1) - setAudioSamplingRate(8000) - setAudioEncodingBitRate(MediaRecorder.AudioEncoder.AAC) - if (mCamera?.getCameraType() == CameraCompat.BACK_CAMERA){ - setOrientationHint(90) - }else{ - setOrientationHint(270) - } - try { - prepare() - }catch (e:IOException){ - e.printStackTrace() - return false - } + return } - return true - } - fun startRecordVideo(){ + if (mSurface!=null && width>0 && height>0) + videoEncoder = VideoEncoderCoder(mSurface!!,width,height,1000000, File(getVideoFileAddress())) + + videoEncoder?.start() isRecordVideo.set(true) - mMediaRecorder?.surface?.let { - initOpenGL(it) - } } fun stopRecordVideo(){ - if(isRecordVideo.get()){ - mMediaRecorder?.stop() - } + videoEncoder?.stop() } - fun relaseRecordVideo(){ - mMediaRecorder?.apply { - stop() - reset() - release() - } - if (isRecordVideo.get()){ - isRecordVideo.set(false) + fun resumeRecordVideo(){ + + } + + fun isRecording():Boolean{ + return isRecordVideo.get() + } + + fun releaseRecordVideo(){ + mExecutor.execute { + if (isRecordVideo.get()) { + videoEncoder?.drainEncoder(true) + videoEncoder?.release() + videoEncoder = null + isRecordVideo.set(false) + } } } @@ -178,16 +146,13 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback mSurfaceTexture?.updateTexImage() mSurfaceTexture?.getTransformMatrix(mMatrix) if (isTakePhoto){ - val photoAddress = if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" - } + val photoAddress = getImageFileAddress() OpenGLJniLib.magicFilterDraw(mMatrix,photoAddress) isTakePhoto =false }else { OpenGLJniLib.magicFilterDraw(mMatrix,"") } + videoEncoder?.drainEncoder(false) } } @@ -222,11 +187,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback @SuppressLint("CheckResult") fun takePhoto(){ - val rootAddress = if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" - } + val rootAddress= getImageFileAddress() // mCamera?.stopPreview() Observable.create(ObservableOnSubscribe { it.onNext(OpenGLJniLib.savePhoto(rootAddress)) @@ -244,4 +205,20 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback Log.e(TAG,it.toString()) }) } + + fun getVideoFileAddress(): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" + } + } + + fun getImageFileAddress():String{ + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + } + } } \ No newline at end of file From f99e14c00470674528e2ca9003390529a1cb5f9f Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 25 Feb 2019 18:06:22 +0800 Subject: [PATCH 04/54] =?UTF-8?q?[2019.2.25]=E6=8F=90=E4=BA=A4=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=BD=95=E5=88=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 35 ++- app/src/main/cpp/camera/CameraFilter.h | 5 +- app/src/main/cpp/egl/EGLCore.cpp | 10 +- app/src/main/cpp/magicjni.cpp | 19 ++ .../cangwang/magic/CameraFilterV2Activity.kt | 8 +- .../com/cangwang/magic/camera/CameraCompat.kt | 4 +- .../cangwang/magic/camera/CameraCompatV21.kt | 7 +- .../com/cangwang/magic/util/OpenGLJniLib.kt | 4 + .../cangwang/magic/video/AudioEncoderCoder.kt | 11 + .../cangwang/magic/video/VideoEncoderCoder.kt | 4 +- .../view/CameraFilterSurfaceCallbackV2.kt | 4 +- .../view/CameraFilterSurfaceCallbackV3.kt | 231 ++++++++++++++++++ 12 files changed, 318 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt create mode 100644 app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index 42667c9..2e36fb7 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -53,6 +53,7 @@ CameraFilter::CameraFilter(ANativeWindow *window): mWindow(window),mEGLCore(new } CameraFilter::CameraFilter(ANativeWindow *window,AAssetManager* assetManager): mWindow(window),mEGLCore(new EGLCore()), + mVideoEGLCore(nullptr),mVideoWindow(nullptr), mAssetManager(assetManager),mTextureId(0),mTextureLoc(0), mMatrixLoc(0),filter(nullptr),cameraInputFilter(nullptr), pool(new std::MagicThreadPool()){ @@ -141,6 +142,34 @@ int CameraFilter::create() { return mTextureId; } +bool CameraFilter::buildVideoSurface(ANativeWindow *window) { + if(mVideoEGLCore == nullptr){ + mVideoWindow = window; + mVideoEGLCore = new EGLCore(); + if (!mVideoEGLCore->buildContext(window)){ + ALOGE("change window error"); + return false; + } else{ + return true; + } + } else{ + return false; + } +} + +void CameraFilter::releaseVideoSurface(){ + if (mVideoEGLCore){ + //清空资源 + mVideoEGLCore->release(); + delete mEGLCore; + mVideoEGLCore = nullptr; + } + if (mVideoWindow){ + ANativeWindow_release(mVideoWindow); + mVideoWindow = nullptr; + } +} + void CameraFilter::change(int width, int height) { //设置视口 glViewport(0,0,width,height); @@ -176,7 +205,11 @@ void CameraFilter::draw(GLfloat *matrix) { filter->onDrawFrame(id,matrix); //缓冲区交换 glFlush(); - mEGLCore->swapBuffer(); + if(mEGLCore!= nullptr) + mEGLCore->swapBuffer(); + if(mVideoEGLCore!= nullptr){ + mVideoEGLCore->swapBuffer(); + } } } diff --git a/app/src/main/cpp/camera/CameraFilter.h b/app/src/main/cpp/camera/CameraFilter.h index 8329ec2..1e4587c 100644 --- a/app/src/main/cpp/camera/CameraFilter.h +++ b/app/src/main/cpp/camera/CameraFilter.h @@ -21,6 +21,8 @@ class CameraFilter:public GLBase{ ~CameraFilter(); void setFilter(GPUImageFilter* gpuImageFilter); int create(); + bool buildVideoSurface(ANativeWindow *window); + void releaseVideoSurface(); void draw(GLfloat *matrix); void change(int width,int height); void stop(); @@ -28,7 +30,6 @@ class CameraFilter:public GLBase{ void setFilter(int type); void setBeautyLevel(int level); bool savePhoto(std::string saveFileAddress); - bool changeVideoEgl(); protected: @@ -37,11 +38,13 @@ class CameraFilter:public GLBase{ CameraInputFilter *cameraInputFilter; AAssetManager *mAssetManager; ANativeWindow *mWindow; + ANativeWindow *mVideoWindow; GLuint mTextureId; GLint mTextureLoc; GLint mMatrixLoc; GLfloat mMatrix[16]; EGLCore *mEGLCore; + EGLCore *mVideoEGLCore; std::MagicThreadPool *pool; }; diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index f4737c5..f93f03b 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -93,11 +93,11 @@ GLboolean EGLCore::buildContext(ANativeWindow *window) { } // 获取eglPresentationTimeANDROID方法的地址 - eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROIDPROC) - eglGetProcAddress("eglPresentationTimeANDROID"); - if (!eglPresentationTimeANDROID) { - ALOGE("eglPresentationTimeANDROID is not available!"); - } +// eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROIDPROC) +// eglGetProcAddress("eglPresentationTimeANDROID"); +// if (!eglPresentationTimeANDROID) { +// ALOGE("eglPresentationTimeANDROID is not available!"); +// } ALOGD("buildContext Succeed"); return GL_TRUE; diff --git a/app/src/main/cpp/magicjni.cpp b/app/src/main/cpp/magicjni.cpp index 0aab6db..3023c56 100644 --- a/app/src/main/cpp/magicjni.cpp +++ b/app/src/main/cpp/magicjni.cpp @@ -162,6 +162,25 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterRelease(JNIEnv *env, jobjec } } +JNIEXPORT void JNICALL +Java_com_cangwang_magic_util_OpenGLJniLib_buildVideoSurface(JNIEnv *env,jobject obj, jobject surface) { + std::unique_lock lock(gMutex); + + ANativeWindow *window = ANativeWindow_fromSurface(env,surface); + if (glCameraFilter!= nullptr){ + glCameraFilter->buildVideoSurface(window); + } +} + +JNIEXPORT void JNICALL +Java_com_cangwang_magic_util_OpenGLJniLib_releaseVideoSurface(JNIEnv *env,jobject obj) { + std::unique_lock lock(gMutex); + + if (glCameraFilter!= nullptr){ + glCameraFilter->releaseVideoSurface(); + } +} + //图片滤镜surfaceView初始化的时候创建 JNIEXPORT jint JNICALL Java_com_cangwang_magic_util_OpenGLJniLib_magicImageFilterCreate(JNIEnv *env, jobject obj, diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index ac4d9bc..bfbbc35 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -5,18 +5,16 @@ import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.app.AlertDialog import android.content.pm.ActivityInfo -import android.graphics.Point import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import android.view.View import android.view.Window import android.view.WindowManager -import android.widget.RelativeLayout import com.cangwang.magic.adapter.FilterAdapter import com.cangwang.magic.camera.CameraCompat import com.cangwang.magic.util.OpenGLJniLib -import com.cangwang.magic.view.CameraFilterSurfaceCallbackV2 +import com.cangwang.magic.view.CameraFilterSurfaceCallbackV3 import kotlinx.android.synthetic.main.activity_camera.* import kotlinx.android.synthetic.main.filter_layout.* @@ -31,7 +29,7 @@ class CameraFilterV2Activity:AppCompatActivity(){ private var mode = MODE_PIC private var mAdapter: FilterAdapter? = null - private var mSurfaceCallback:CameraFilterSurfaceCallbackV2?=null + private var mSurfaceCallback:CameraFilterSurfaceCallbackV3?=null private var beautyLevel:Int = 0 var mCamera: CameraCompat?=null @@ -113,7 +111,7 @@ class CameraFilterV2Activity:AppCompatActivity(){ private fun initCamera(){ mCamera = CameraCompat.newInstance(this) - mSurfaceCallback = CameraFilterSurfaceCallbackV2(mCamera) + mSurfaceCallback = CameraFilterSurfaceCallbackV3(mCamera) glsurfaceview_camera.holder.addCallback(mSurfaceCallback) mCamera?.startPreview() } diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt index 3037369..98e3d4f 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt @@ -19,7 +19,6 @@ abstract class CameraCompat(protected var mContext: Context) { private var mSwitchFlag: Boolean = false protected var mSurfaceTexture: SurfaceTexture? = null - var surface:Surface?=null /** * 是否支持闪光灯 */ @@ -61,9 +60,8 @@ abstract class CameraCompat(protected var mContext: Context) { initCameraInfo() } - fun setSurfaceTexture(texture: SurfaceTexture,surface: Surface?=null) { + fun setSurfaceTexture(texture: SurfaceTexture) { this.mSurfaceTexture = texture - this.surface =surface } protected fun setFrontCameraId(id: String) { diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt index a96b29e..e149e0d 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt @@ -133,11 +133,8 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { try { mCaptureSession?.close() - mSurface = if(surface==null) { - Surface(mSurfaceTexture) - }else{ - surface - } + mSurface = Surface(mSurfaceTexture) + outputSize?.let { mSurfaceTexture?.setDefaultBufferSize(it.width, it.height) } diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index 2a41f75..a72df2c 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -96,4 +96,8 @@ object OpenGLJniLib{ * 编辑保存图片 */ external fun saveImage(address: String):Boolean + + external fun buildVideoSurface(surface:Surface) + + external fun releaseVideoSurface() } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt new file mode 100644 index 0000000..3897ae0 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt @@ -0,0 +1,11 @@ +package com.cangwang.magic.video + +import android.media.MediaCodec +import android.media.MediaRecorder + +class AudioEncoderCoder{ + private lateinit var audioEncoder:MediaRecorder.AudioEncoder + constructor(){ +// audioEncoder = MediaCodec.createEncoderByType(MediaCodec.) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index 34393f3..f177693 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -24,7 +24,7 @@ class VideoEncoderCoder{ private var mTrackIndex = -1 private var mMuxerStarted = false - constructor(inputSurface:Surface,width:Int,height:Int,bitRate:Int,outFile:File){ + constructor(width:Int,height:Int,bitRate:Int,outFile:File){ val format = MediaFormat.createVideoFormat(MINE_TYPE,width,height) format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) format.setInteger(MediaFormat.KEY_BIT_RATE,bitRate) @@ -34,7 +34,7 @@ class VideoEncoderCoder{ mEncoder = MediaCodec.createEncoderByType(MINE_TYPE) mEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) - mInputSurface = inputSurface + mInputSurface = mEncoder.createInputSurface() mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) mTrackIndex = -1 diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt index 9a08f5a..9b72848 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt @@ -75,7 +75,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback mSurface = Surface(mSurfaceTexture) try { mSurfaceTexture?.let { - mCamera?.setSurfaceTexture(it,mSurface) + mCamera?.setSurfaceTexture(it) } doStartPreview() }catch (e:IOException){ @@ -91,7 +91,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback } if (mSurface!=null && width>0 && height>0) - videoEncoder = VideoEncoderCoder(mSurface!!,width,height,1000000, File(getVideoFileAddress())) + videoEncoder = VideoEncoderCoder(width,height,1000000, File(getVideoFileAddress())) videoEncoder?.start() isRecordVideo.set(true) diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt new file mode 100644 index 0000000..ee2ed71 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -0,0 +1,231 @@ +package com.cangwang.magic.view + + +import android.annotation.SuppressLint +import android.graphics.SurfaceTexture +import android.media.MediaRecorder +import android.os.Build +import android.os.Environment +import android.util.Log +import android.view.Surface +import android.view.SurfaceHolder +import android.widget.Toast +import com.cangwang.magic.BaseApplication +import com.cangwang.magic.camera.CameraCompat +import com.cangwang.magic.util.OpenGLJniLib +import com.cangwang.magic.video.VideoEncoderCoder +import io.reactivex.Observable +import io.reactivex.ObservableOnSubscribe +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import java.io.File +import java.io.IOException + +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Created by zjl on 2018/10/12. + */ +class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback{ + private val mExecutor = Executors.newSingleThreadExecutor() + + private val TAG= CameraFilterSurfaceCallbackV3::class.java.simpleName!! + private var mSurfaceTexture:SurfaceTexture?=null + private var mSurface:Surface?=null + private var mCamera=camera + private val mMatrix = FloatArray(16) + private var width = 0 + private var height = 0 + private var isTakePhoto = false + + private var mMediaRecorder:MediaRecorder?=null + private var isRecordVideo = AtomicBoolean() + private var previewSurface:Surface?=null + private var videoEncoder:VideoEncoderCoder ?=null + + override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { + this.width = width + this.height = height + changeOpenGL(width,height) + } + + override fun surfaceDestroyed(holder: SurfaceHolder?) { + mCamera?.stopPreview(true) + releaseOpenGL() + } + + override fun surfaceCreated(holder: SurfaceHolder?) { + holder?.let { + previewSurface = it.surface + initOpenGL(it.surface) + mSurface = it.surface + } + } + + fun initOpenGL(surface: Surface){ + mExecutor.execute { + val textureId = OpenGLJniLib.magicFilterCreate(surface,BaseApplication.context.assets) +// OpenGLJniLib.setFilterType(MagicFilterType.NONE.ordinal) + if (textureId < 0){ + Log.e(TAG, "surfaceCreated init OpenGL ES failed!") + return@execute + } + mSurfaceTexture = SurfaceTexture(textureId) + mSurfaceTexture?.setOnFrameAvailableListener { drawOpenGL() } + try { + mSurfaceTexture?.let { + mCamera?.setSurfaceTexture(it) + } + doStartPreview() + }catch (e:IOException){ + Log.e(TAG,e.localizedMessage) + releaseOpenGL() + } + } + } + + fun startRecordVideo(){ + mExecutor.execute { + if (isRecordVideo.get()){ + return@execute + } + + if (width>0 && height>0) + videoEncoder = VideoEncoderCoder(width,height,1000000, File(getVideoFileAddress())) + videoEncoder?.let{ + OpenGLJniLib.buildVideoSurface(it.getInputSurface()) + it.start() + isRecordVideo.set(true) + } + } + } + + fun stopRecordVideo(){ + videoEncoder?.stop() + } + + fun resumeRecordVideo(){ + + } + + fun isRecording():Boolean{ + return isRecordVideo.get() + } + + fun releaseRecordVideo(){ + mExecutor.execute { + if (isRecordVideo.get()) { + videoEncoder?.drainEncoder(true) + videoEncoder?.release() + videoEncoder = null + OpenGLJniLib.releaseVideoSurface() + isRecordVideo.set(false) + } + } + } + + fun changeCamera(camera:CameraCompat? ){ + mExecutor.execute { + mCamera = camera + try { + mSurfaceTexture?.let { + mCamera?.setSurfaceTexture(it) + } + doStartPreview() + } catch (e: IOException) { + Log.e(TAG, e.localizedMessage) + releaseOpenGL() + } + } + } + + fun changeOpenGL(width:Int,height:Int){ + mExecutor.execute { + OpenGLJniLib.magicFilterChange(width,height) + } + } + + fun drawOpenGL(){ + mExecutor.execute { + mSurfaceTexture?.let{ + it.updateTexImage() + it.getTransformMatrix(mMatrix) + if (isTakePhoto){ + val photoAddress = getImageFileAddress() + OpenGLJniLib.magicFilterDraw(mMatrix,photoAddress) + isTakePhoto =false + }else { + OpenGLJniLib.magicFilterDraw(mMatrix,"") + } + videoEncoder?.drainEncoder(false) + } + } + } + + fun releaseOpenGL(){ + mExecutor.execute { + OpenGLJniLib.magicFilterRelease() + mSurfaceTexture?.release() + mSurfaceTexture=null + mCamera =null + } + } + + fun setFilterType(type:Int){ + mExecutor.execute { + OpenGLJniLib.setFilterType(type) + } + } + + fun doStartPreview(){ + mCamera?.startPreview(object :CameraCompat.CameraStateCallBack{ + override fun onConfigured() { + if (isRecordVideo.get()){ + mMediaRecorder?.start() + } + } + + override fun onConfigureFailed() { + + } + }) + } + + @SuppressLint("CheckResult") + fun takePhoto(){ + val rootAddress= getImageFileAddress() +// mCamera?.stopPreview() + Observable.create(ObservableOnSubscribe { + it.onNext(OpenGLJniLib.savePhoto(rootAddress)) + }).subscribeOn(Schedulers.from(mExecutor)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ +// mCamera?.startPreview() + if (!it){ + Toast.makeText(BaseApplication.context,"save fail",Toast.LENGTH_SHORT).show() + }else{ + Toast.makeText(BaseApplication.context,"save success",Toast.LENGTH_SHORT).show() + } + },{ +// mCamera?.startPreview() + Log.e(TAG,it.toString()) + }) + } + + fun getVideoFileAddress(): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" + } + } + + fun getImageFileAddress():String{ + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + } + } +} \ No newline at end of file From ad91aee1bf42907334e38b0c84ba2a5e1f7e26ed Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 25 Feb 2019 18:42:39 +0800 Subject: [PATCH 05/54] =?UTF-8?q?[2019.2.25]=E5=8A=A0=E5=85=A5=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=BD=95=E5=88=B6=E7=9A=84eglcontext=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraEngine.cpp | 2 +- app/src/main/cpp/camera/CameraFilter.cpp | 4 ++-- app/src/main/cpp/egl/EGLCore.cpp | 14 +++++++++----- app/src/main/cpp/egl/EGLCore.h | 5 +++-- app/src/main/cpp/image/ImageFilter.cpp | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/main/cpp/camera/CameraEngine.cpp b/app/src/main/cpp/camera/CameraEngine.cpp index 21eae43..7207406 100644 --- a/app/src/main/cpp/camera/CameraEngine.cpp +++ b/app/src/main/cpp/camera/CameraEngine.cpp @@ -59,7 +59,7 @@ CameraEngine::~CameraEngine() { } int CameraEngine::create() { - if (!mEGLCore->buildContext(mWindow)){ + if (!mEGLCore->buildContext(mWindow,EGL_NO_CONTEXT)){ return -1; } std::string *vShader = readShaderFromAsset(mAssetManager,"camera.vert"); diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index 2e36fb7..73717c7 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -124,7 +124,7 @@ int CameraFilter::create() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); - if (!mEGLCore->buildContext(mWindow)){ + if (!mEGLCore->buildContext(mWindow,EGL_NO_CONTEXT)){ return -1; } @@ -146,7 +146,7 @@ bool CameraFilter::buildVideoSurface(ANativeWindow *window) { if(mVideoEGLCore == nullptr){ mVideoWindow = window; mVideoEGLCore = new EGLCore(); - if (!mVideoEGLCore->buildContext(window)){ + if (!mVideoEGLCore->buildContext(window,mEGLCore->mContext)){ ALOGE("change window error"); return false; } else{ diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index f93f03b..ad34e89 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -22,7 +22,7 @@ EGLCore::~EGLCore() { mContext = EGL_NO_CONTEXT; } -GLboolean EGLCore::buildContext(ANativeWindow *window) { +GLboolean EGLCore::buildContext(ANativeWindow *window, EGLContext context) { //与本地窗口通信 mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (mDisplay == EGL_NO_DISPLAY){ @@ -66,10 +66,14 @@ GLboolean EGLCore::buildContext(ANativeWindow *window) { //只使用opengles3 GLint contextAttrib[] = {EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE}; // EGL_NO_CONTEXT表示不向其它的context共享资源 - mContext = eglCreateContext(mDisplay,config,EGL_NO_CONTEXT,contextAttrib); - if (mContext == EGL_NO_CONTEXT){ - ALOGE("eglCreateContext failed: %d",eglGetError()); - return GL_FALSE; + if(context && context!= EGL_NO_CONTEXT){ + mContext = context; + } else { + mContext = eglCreateContext(mDisplay, config, EGL_NO_CONTEXT, contextAttrib); + if (mContext == EGL_NO_CONTEXT){ + ALOGE("eglCreateContext failed: %d",eglGetError()); + return GL_FALSE; + } } EGLint format = 0; diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index 286acaa..e2660da 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -16,7 +16,7 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d ~EGLCore(); - GLboolean buildContext(ANativeWindow *window); + GLboolean buildContext(ANativeWindow *window,EGLContext context); void setPresentationTime(long nsecs); @@ -24,12 +24,13 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d void release(); + EGLContext mContext; + protected: private: EGLDisplay mDisplay; EGLSurface mSurface; - EGLContext mContext; EGL_PRESENTATION_TIME_ANDROIDPROC eglPresentationTimeANDROID = NULL; }; diff --git a/app/src/main/cpp/image/ImageFilter.cpp b/app/src/main/cpp/image/ImageFilter.cpp index 36daa27..73477d0 100644 --- a/app/src/main/cpp/image/ImageFilter.cpp +++ b/app/src/main/cpp/image/ImageFilter.cpp @@ -95,7 +95,7 @@ int ImageFilter::create() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); - if (!mEGLCore->buildContext(mWindow)){ + if (!mEGLCore->buildContext(mWindow,EGL_NO_CONTEXT)){ return -1; } From bf4aebc3dc7bd2eddeae7851597698df6d88d8dc Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Wed, 27 Feb 2019 17:44:09 +0800 Subject: [PATCH 06/54] =?UTF-8?q?[2019.2.27]=E6=9B=B4=E6=96=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=88=87=E6=8D=A2=E5=BD=95=E5=83=8F=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 4 +- app/src/main/cpp/egl/EGLCore.cpp | 95 ++++++++++++++++++- app/src/main/cpp/egl/EGLCore.h | 1 + .../cangwang/magic/video/VideoEncoderCoder.kt | 13 ++- 4 files changed, 101 insertions(+), 12 deletions(-) diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index 73717c7..f72b037 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -146,7 +146,7 @@ bool CameraFilter::buildVideoSurface(ANativeWindow *window) { if(mVideoEGLCore == nullptr){ mVideoWindow = window; mVideoEGLCore = new EGLCore(); - if (!mVideoEGLCore->buildContext(window,mEGLCore->mContext)){ + if (!mVideoEGLCore->buildVideoContext(window,eglGetCurrentContext())){ ALOGE("change window error"); return false; } else{ @@ -161,7 +161,7 @@ void CameraFilter::releaseVideoSurface(){ if (mVideoEGLCore){ //清空资源 mVideoEGLCore->release(); - delete mEGLCore; + delete mVideoEGLCore; mVideoEGLCore = nullptr; } if (mVideoWindow){ diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index ad34e89..824a90f 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -46,7 +46,7 @@ GLboolean EGLCore::buildContext(ANativeWindow *window, EGLContext context) { EGL_GREEN_SIZE,6, //指定G大小 EGL_BLUE_SIZE,5, //指定B大小 EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT_KHR, //渲染类型,为相机扩展类型 - EGL_SURFACE_TYPE,EGL_WINDOW_BIT, //绘图类型, +// EGL_SURFACE_TYPE,EGL_WINDOW_BIT, //绘图类型, EGL_NONE }; @@ -103,7 +103,95 @@ GLboolean EGLCore::buildContext(ANativeWindow *window, EGLContext context) { // ALOGE("eglPresentationTimeANDROID is not available!"); // } - ALOGD("buildContext Succeed"); + ALOGD("buildVideoContext Succeed"); + return GL_TRUE; +} + +GLboolean EGLCore::buildVideoContext(ANativeWindow *window, EGLContext context) { + //与本地窗口通信 + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mDisplay == EGL_NO_DISPLAY){ + ALOGE("eglGetDisplay failed: %d",eglGetError()); + return GL_FALSE; + } + + GLint majorVersion; + GLint minorVersion; + //获取支持最低和最高版本 + if (!eglInitialize(mDisplay,&majorVersion,&minorVersion)){ + ALOGE("eglInitialize failed: %d",eglGetError()); + return GL_FALSE; + } + + EGLConfig config; + EGLint numConfigs = 0; + //颜色使用565,读写类型需要egl扩展 + EGLint attribList[] = { + EGL_RED_SIZE,5, //指定RGB中的R大小(bits) + EGL_GREEN_SIZE,6, //指定G大小 + EGL_BLUE_SIZE,5, //指定B大小 + EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT_KHR, //渲染类型,为相机扩展类型 +// EGL_SURFACE_TYPE,EGL_WINDOW_BIT, //绘图类型, + EGL_RECORDABLE_ANDROID,EGL_ANDROID_recordable, + EGL_NONE + }; + + //让EGL推荐匹配的EGLConfig + if(!eglChooseConfig(mDisplay,attribList,&config,1,&numConfigs)){ + ALOGE("eglChooseConfig failed: %d",eglGetError()); + return GL_FALSE; + } + + //找不到匹配的 + if (numConfigs <1){ + ALOGE("eglChooseConfig get config number less than one"); + return GL_FALSE; + } + + //创建渲染上下文 + //只使用opengles3 + GLint contextAttrib[] = {EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE}; + // EGL_NO_CONTEXT表示不向其它的context共享资源 + mContext = eglCreateContext(mDisplay, config, context, contextAttrib); + if (mContext == EGL_NO_CONTEXT){ + ALOGE("eglCreateContext failed: %d",eglGetError()); + return GL_FALSE; + } + + + EGLint format = 0; + if (!eglGetConfigAttrib(mDisplay,config,EGL_NATIVE_VISUAL_ID,&format)){ + ALOGE("eglGetConfigAttrib failed: %d",eglGetError()); + return GL_FALSE; + } + ANativeWindow_setBuffersGeometry(window,0,0,format); + + //创建On-Screen 渲染区域 + mSurface = eglCreateWindowSurface(mDisplay,config,window,0); + if (mSurface == EGL_NO_SURFACE){ + ALOGE("eglCreateWindowSurface failed: %d",eglGetError()); + return GL_FALSE; + } + +// if (!eglMakeCurrent(mDisplay,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)){ +// ALOGE("free eglCurrent failed: %d",eglGetError()); +// return GL_FALSE; +// } + + //把EGLContext和EGLSurface关联起来,单缓冲只使用了一个surface + if (!eglMakeCurrent(mDisplay,mSurface,mSurface,mContext)){ + ALOGE("eglMakeCurrent failed: %d",eglGetError()); + return GL_FALSE; + } + + // 获取eglPresentationTimeANDROID方法的地址 +// eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROIDPROC) +// eglGetProcAddress("eglPresentationTimeANDROID"); +// if (!eglPresentationTimeANDROID) { +// ALOGE("eglPresentationTimeANDROID is not available!"); +// } + + ALOGD("buildVideoContext Succeed"); return GL_TRUE; } @@ -125,7 +213,8 @@ void EGLCore::swapBuffer() { //3)Dequeue一块新的buffer,并等待fence。如果等待超时,就将buffer cancel掉。 //4)按需重新计算buffer //5)Lock buffer,这样就实现page flip,也就是swapbuffer - eglSwapBuffers(mDisplay,mSurface); + if (mDisplay!=EGL_NO_DISPLAY && mSurface!= EGL_NO_SURFACE) + eglSwapBuffers(mDisplay,mSurface); } void EGLCore::release() { diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index e2660da..f305e54 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -17,6 +17,7 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d ~EGLCore(); GLboolean buildContext(ANativeWindow *window,EGLContext context); + GLboolean buildVideoContext(ANativeWindow *window, EGLContext context); void setPresentationTime(long nsecs); diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index f177693..780fac0 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -1,5 +1,6 @@ package com.cangwang.magic.video + import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaFormat @@ -9,7 +10,7 @@ import android.view.Surface import java.io.File import java.lang.RuntimeException -class VideoEncoderCoder{ +class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { companion object { const val TAG = "VideoEncoderCoder" const val MINE_TYPE = "video/avc" @@ -17,25 +18,23 @@ class VideoEncoderCoder{ const val IFRAME_INTERVAL = 5 } - private lateinit var mInputSurface:Surface - private lateinit var mMuxer:MediaMuxer - private lateinit var mEncoder:MediaCodec + private var mInputSurface:Surface + private var mMuxer:MediaMuxer + private var mEncoder:MediaCodec private var mBufferInfo:MediaCodec.BufferInfo = MediaCodec.BufferInfo() private var mTrackIndex = -1 private var mMuxerStarted = false - constructor(width:Int,height:Int,bitRate:Int,outFile:File){ + init { val format = MediaFormat.createVideoFormat(MINE_TYPE,width,height) format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) format.setInteger(MediaFormat.KEY_BIT_RATE,bitRate) format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE) format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL) Log.d(TAG, "format: $format") - mEncoder = MediaCodec.createEncoderByType(MINE_TYPE) mEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) mInputSurface = mEncoder.createInputSurface() - mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) mTrackIndex = -1 mMuxerStarted = false From 5db30ed8ca56976d53d6accaa0cf5618298a3404 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 28 Feb 2019 11:32:34 +0800 Subject: [PATCH 07/54] =?UTF-8?q?[2019.2.27]=E6=9B=B4=E6=96=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=88=87=E6=8D=A2=E5=BD=95=E5=83=8F=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/CMakeLists.txt | 1 + app/src/main/cpp/magicjni.cpp | 49 ++++- app/src/main/cpp/video/VideoFilter.cpp | 195 ++++++++++++++++++ app/src/main/cpp/video/VideoFilter.h | 47 +++++ .../com/cangwang/magic/util/OpenGLJniLib.kt | 13 +- .../magic/video/TextureMovieEncoder.kt | 9 + .../view/CameraFilterSurfaceCallbackV3.kt | 60 +++--- 7 files changed, 342 insertions(+), 32 deletions(-) create mode 100644 app/src/main/cpp/video/VideoFilter.cpp create mode 100644 app/src/main/cpp/video/VideoFilter.h create mode 100644 app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 48e5d99..6cba220 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -45,6 +45,7 @@ add_library( # Sets the name of the library. src/main/cpp/camera/CameraEngine.cpp src/main/cpp/image/ImageFilter.cpp src/main/cpp/image/ImageInput.cpp + src/main/cpp/video/VideoFilter.cpp src/main/cpp/egl/EGLCore.cpp src/main/cpp/filter/advanced/MagicAmaroFilter.cpp src/main/cpp/filter/advanced/MagicAntiqueFilter.cpp diff --git a/app/src/main/cpp/magicjni.cpp b/app/src/main/cpp/magicjni.cpp index 3023c56..8959b4b 100644 --- a/app/src/main/cpp/magicjni.cpp +++ b/app/src/main/cpp/magicjni.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "src/main/cpp/camera/CameraEngine.h" #include "src/main/cpp/camera/CameraFilter.h" @@ -21,6 +22,7 @@ extern "C" { std::mutex gMutex; CameraEngine *glCamera = nullptr; CameraFilter *glCameraFilter = nullptr; +VideoFilter *glVideoFilter = nullptr; ImageFilter *glImageFilter = nullptr; AAssetManager *aAssetManager = nullptr; @@ -120,6 +122,9 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterSet(JNIEnv *env, jobject ob } AAssetManager *manager = AAssetManager_fromJava(env,assetManager); glCameraFilter->setFilter(manager); + if(glVideoFilter!= nullptr){ + glVideoFilter->setFilter(manager); + } } //窗口大小设置,SurfaceView初始化后会触发一次 @@ -133,6 +138,10 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterChange(JNIEnv *env, jobject } //更改窗口大小 glCameraFilter->change(width,height); + if(glVideoFilter!= nullptr){ + glVideoFilter->change(width,height); + } + } JNIEXPORT void JNICALL @@ -163,21 +172,50 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterRelease(JNIEnv *env, jobjec } JNIEXPORT void JNICALL -Java_com_cangwang_magic_util_OpenGLJniLib_buildVideoSurface(JNIEnv *env,jobject obj, jobject surface) { +Java_com_cangwang_magic_util_OpenGLJniLib_buildVideoSurface(JNIEnv *env,jobject obj, jobject surface,jint textureId,jobject assetManager) { std::unique_lock lock(gMutex); + if(glVideoFilter){ //停止视频采集并销毁 + glVideoFilter->stop(); + delete glVideoFilter; + glVideoFilter = nullptr; + } + //初始化native window ANativeWindow *window = ANativeWindow_fromSurface(env,surface); - if (glCameraFilter!= nullptr){ - glCameraFilter->buildVideoSurface(window); + //初始化app内获取数据管理 + aAssetManager= AAssetManager_fromJava(env,assetManager); + //初始化相机采集 + glVideoFilter = new VideoFilter(window,aAssetManager); + //创建 + glVideoFilter->create(textureId); +} + +JNIEXPORT void JNICALL +Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoDraw(JNIEnv *env, jobject obj,jfloatArray matrix_,jstring address) { + //加锁 + std::unique_lock lock(gMutex); + //获取摄像头矩阵 + jfloat *matrix = env->GetFloatArrayElements(matrix_,NULL); + + //如果为空,就判断错误,中断 + if (!glVideoFilter){ + ALOGE("draw error, glCameraFilter is null"); + return; } + //摄像头采集画图 + glVideoFilter->draw(matrix); + //释放矩阵数据 + env->ReleaseFloatArrayElements(matrix_,matrix,0); } JNIEXPORT void JNICALL Java_com_cangwang_magic_util_OpenGLJniLib_releaseVideoSurface(JNIEnv *env,jobject obj) { std::unique_lock lock(gMutex); - if (glCameraFilter!= nullptr){ - glCameraFilter->releaseVideoSurface(); + if(glVideoFilter){ //停止摄像头采集并销毁 + glVideoFilter->stop(); + delete glVideoFilter; + glVideoFilter = nullptr; } } @@ -189,6 +227,7 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicImageFilterCreate(JNIEnv *env, jo if(glImageFilter){ //停止摄像头采集并销毁 glImageFilter->stop(); delete glImageFilter; + glImageFilter = nullptr; } //初始化native window diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp new file mode 100644 index 0000000..e1e94e8 --- /dev/null +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -0,0 +1,195 @@ +#include +#include +#include "VideoFilter.h" +#include "src/main/cpp/utils/OpenglUtils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define LOG_TAG "VideoFilter" +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +/** + * 相机滤镜管理 + * cangwang 2018.12.1 + */ +const static GLfloat VERTICES[]= { + -1.0f,1.0f, + 1.0f,1.0f, + -1.0f,-1.0f, + 1.0f,-1.0f +}; + +const static GLfloat TEX_COORDS[]={ + 0.0f,1.0f, + 1.0f,1.0f, + 0.0f,0.0f, + 1.0f,0.0f +}; + +const static GLuint ATTRIB_POSITION = 0; +const static GLuint ATTRIB_TEXCOORD = 1; +const static GLuint VERTEX_NUM = 4; +const static GLuint VERTEX_POS_SIZE = 2; +const static GLuint TEX_COORD_POS_SZIE = 2; + +VideoFilter::VideoFilter(ANativeWindow *window): mWindow(window),mEGLCore(new EGLCore()), + mAssetManager(nullptr),mTextureId(0),mTextureLoc(0), + mMatrixLoc(0){ + //清空mMatrix数组 + memset(mMatrix,0, sizeof(mMatrix)); + mMatrix[0] = 1; + mMatrix[5] = 1; + mMatrix[10] = 1; + mMatrix[15] = 1; +} + +VideoFilter::VideoFilter(ANativeWindow *window,AAssetManager* assetManager): mWindow(window),mEGLCore(new EGLCore()),mVideoWindow(nullptr), + mAssetManager(assetManager),mTextureId(0),mTextureLoc(0), + mMatrixLoc(0),filter(nullptr),cameraInputFilter(nullptr), + pool(new std::MagicThreadPool()){ + //清空mMatrix数组 + memset(mMatrix,0, sizeof(mMatrix)); + mMatrix[0] = 1; + mMatrix[5] = 1; + mMatrix[10] = 1; + mMatrix[15] = 1; + + setFilter(assetManager); +} + +VideoFilter::~VideoFilter() { + if (filter!= nullptr){ + filter->destroy(); + delete filter; + filter = nullptr; + } + + if (mEGLCore){ + //清空资源 + mEGLCore->release(); + delete mEGLCore; + mEGLCore = nullptr; + } + if (mWindow){ + ANativeWindow_release(mWindow); + mWindow = nullptr; + } + + mAssetManager = nullptr; + //释放线程池 + free(pool); +} + +void VideoFilter::setFilter(AAssetManager* assetManager) { + if(filter != nullptr){ + filter->destroy(); + } + filter = new MagicNoneFilter(assetManager); + filter->setPool(pool); + ALOGD("set filter success"); +} + +void VideoFilter::setFilter(int type) { + GPUImageFilter* filter = initFilters(type,mAssetManager); + setFilter(filter); +} + +int VideoFilter::create(int textureId) { + glDisable(GL_DITHER); + glClearColor(0,0,0,0); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + if (!mEGLCore->buildVideoContext(mWindow,eglGetCurrentContext())){ + return -1; + } + + //滤镜初始化 + if (filter!= nullptr) + filter->init(); + mTextureId = textureId; + ALOGD("get textureId success"); + + return mTextureId; +} + +void VideoFilter::change(int width, int height) { + //设置视口 + glViewport(0,0,width,height); + mWidth = width; + mHeight = height; + if (cameraInputFilter!= nullptr){ + if (cameraInputFilter!= nullptr){ + //触发输入大小更新 + cameraInputFilter->onInputSizeChanged(width, height); + //初始化帧缓冲 + cameraInputFilter->initCameraFrameBuffer(width,height); + } + if (filter != nullptr){ + //初始化滤镜的大小 + filter->onInputSizeChanged(width,height); + } else{ + cameraInputFilter->destroyCameraFrameBuffers(); + } + } +} + + +void VideoFilter::draw(GLfloat *matrix) { + //清屏 + glClearColor(0,0,0,0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (cameraInputFilter != nullptr){ +// cameraInputFilter->onDrawFrame(mTextureId,matrix,VERTICES,TEX_COORDS); + //获取帧缓冲id + GLuint id = cameraInputFilter->onDrawToTexture(mTextureId,matrix); + if (filter != nullptr) + //通过滤镜filter绘制 + filter->onDrawFrame(id,matrix); + //缓冲区交换 + glFlush(); + if(mEGLCore!= nullptr) + mEGLCore->swapBuffer(); + } +} + +void VideoFilter::stop() { + //删除纹理 + glDeleteTextures(1,&mTextureId); +} + +void VideoFilter::setFilter(GPUImageFilter* gpuImageFilter) { + if(filter != nullptr){ + filter->destroy(); + filter= nullptr; + } + filter = gpuImageFilter; + filter->setPool(pool); + ALOGD("set filter success"); + if (filter!= nullptr) + filter->init(); + filter->onInputSizeChanged(cameraInputFilter->mScreenWidth,cameraInputFilter->mScreenHeight); +} + +void VideoFilter::setBeautyLevel(int level) { + if (cameraInputFilter != nullptr){ + cameraInputFilter->setBeautyLevel(level); + } +} + +bool VideoFilter::savePhoto(std::string saveFileAddress){ + if(filter != nullptr){ + return filter->savePhoto(saveFileAddress); + } + return false; +} + diff --git a/app/src/main/cpp/video/VideoFilter.h b/app/src/main/cpp/video/VideoFilter.h new file mode 100644 index 0000000..0847075 --- /dev/null +++ b/app/src/main/cpp/video/VideoFilter.h @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include "src/main/cpp/utils/OpenglUtils.h" +#include "src/main/cpp/egl/GLBase.h" +#include "src/main/cpp/egl/EGLCore.h" +#include +#include +#include +#include + +/** + * cangwang 2018.12.1 + */ +class VideoFilter:public GLBase{ +public: + VideoFilter(ANativeWindow *window); + VideoFilter(ANativeWindow *window,AAssetManager *assetManager); + ~VideoFilter(); + void setFilter(GPUImageFilter* gpuImageFilter); + int create(int textureId); + void draw(GLfloat *matrix); + void change(int width,int height); + void stop(); + void setFilter(AAssetManager* assetManager); + void setFilter(int type); + void setBeautyLevel(int level); + bool savePhoto(std::string saveFileAddress); + +protected: + +private: + GPUImageFilter *filter; + CameraInputFilter *cameraInputFilter; + AAssetManager *mAssetManager; + ANativeWindow *mWindow; + ANativeWindow *mVideoWindow; + GLuint mTextureId; + GLint mTextureLoc; + GLint mMatrixLoc; + GLfloat mMatrix[16]; + EGLCore *mEGLCore; + std::MagicThreadPool *pool; +}; + diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index a72df2c..20f668e 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -97,7 +97,18 @@ object OpenGLJniLib{ */ external fun saveImage(address: String):Boolean - external fun buildVideoSurface(surface:Surface) + /** + * 创建视频Surface + */ + external fun buildVideoSurface(surface:Surface,textureId:Int,manager: AssetManager) + /** + * 视频绘制 + */ + external fun magicVideoDraw(matrix:FloatArray) + + /** + * 释放视频Surface + */ external fun releaseVideoSurface() } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt new file mode 100644 index 0000000..1d39e31 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -0,0 +1,9 @@ +package com.cangwang.magic.video + +class TextureMovieEncoder(videoEncoder: VideoEncoderCoder){ + private var videoEncoder:VideoEncoderCoder = videoEncoder + + fun prepareCoder(prviewWidthA:Int,preivewHeight:Int){ + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index ee2ed71..890c93f 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -38,11 +38,20 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback private var width = 0 private var height = 0 private var isTakePhoto = false + private var textureId:Int = -1 private var mMediaRecorder:MediaRecorder?=null private var isRecordVideo = AtomicBoolean() private var previewSurface:Surface?=null private var videoEncoder:VideoEncoderCoder ?=null + private var recordStatus = RECORD_IDLE + + companion object { + val RECORD_IDLE = 0 + val RECORD_START = 1 + val RECORD_RECORDING = 2 + val RECORD_STOP = 3 + } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width @@ -65,7 +74,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback fun initOpenGL(surface: Surface){ mExecutor.execute { - val textureId = OpenGLJniLib.magicFilterCreate(surface,BaseApplication.context.assets) + textureId = OpenGLJniLib.magicFilterCreate(surface,BaseApplication.context.assets) // OpenGLJniLib.setFilterType(MagicFilterType.NONE.ordinal) if (textureId < 0){ Log.e(TAG, "surfaceCreated init OpenGL ES failed!") @@ -86,19 +95,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback } fun startRecordVideo(){ - mExecutor.execute { - if (isRecordVideo.get()){ - return@execute - } - - if (width>0 && height>0) - videoEncoder = VideoEncoderCoder(width,height,1000000, File(getVideoFileAddress())) - videoEncoder?.let{ - OpenGLJniLib.buildVideoSurface(it.getInputSurface()) - it.start() - isRecordVideo.set(true) - } - } + recordStatus = RECORD_START } fun stopRecordVideo(){ @@ -114,15 +111,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback } fun releaseRecordVideo(){ - mExecutor.execute { - if (isRecordVideo.get()) { - videoEncoder?.drainEncoder(true) - videoEncoder?.release() - videoEncoder = null - OpenGLJniLib.releaseVideoSurface() - isRecordVideo.set(false) - } - } + recordStatus = RECORD_STOP } fun changeCamera(camera:CameraCompat? ){ @@ -150,6 +139,27 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback mExecutor.execute { mSurfaceTexture?.let{ it.updateTexImage() + + if(recordStatus == RECORD_START) { + if (width > 0 && height > 0) + videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) + videoEncoder?.apply { + OpenGLJniLib.buildVideoSurface(getInputSurface(),textureId,BaseApplication.context.assets) + start() + isRecordVideo.set(true) + } + recordStatus = RECORD_RECORDING + }else if (recordStatus == RECORD_STOP){ + if (isRecordVideo.get()) { + videoEncoder?.drainEncoder(true) + videoEncoder?.release() + videoEncoder = null + OpenGLJniLib.releaseVideoSurface() + isRecordVideo.set(false) + recordStatus = RECORD_IDLE + } + } + it.getTransformMatrix(mMatrix) if (isTakePhoto){ val photoAddress = getImageFileAddress() @@ -181,9 +191,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback fun doStartPreview(){ mCamera?.startPreview(object :CameraCompat.CameraStateCallBack{ override fun onConfigured() { - if (isRecordVideo.get()){ - mMediaRecorder?.start() - } + } override fun onConfigureFailed() { From d2a8afbdd882dc5123ff377738cbb56800d36bc8 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 28 Feb 2019 11:50:57 +0800 Subject: [PATCH 08/54] =?UTF-8?q?[2019.2.28]=E6=9B=B4=E6=96=B0=E5=BD=95?= =?UTF-8?q?=E5=83=8F=E7=9A=84=E6=BB=A4=E9=95=9C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/magicjni.cpp | 11 ++++++++++- .../com/cangwang/magic/util/OpenGLJniLib.kt | 5 +++++ .../view/CameraFilterSurfaceCallbackV3.kt | 18 +++++++++++++----- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/src/main/cpp/magicjni.cpp b/app/src/main/cpp/magicjni.cpp index 8959b4b..fc04712 100644 --- a/app/src/main/cpp/magicjni.cpp +++ b/app/src/main/cpp/magicjni.cpp @@ -199,7 +199,7 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoDraw(JNIEnv *env, jobject ob //如果为空,就判断错误,中断 if (!glVideoFilter){ - ALOGE("draw error, glCameraFilter is null"); + ALOGE("draw error, glVideoFilter is null"); return; } //摄像头采集画图 @@ -299,6 +299,15 @@ Java_com_cangwang_magic_util_OpenGLJniLib_setFilterType(JNIEnv *env, jobject obj if(glCameraFilter!= nullptr) glCameraFilter->setFilter(type); else ALOGE("filter set error, glCameraFilter is null"); + if(glVideoFilter!= nullptr) + glVideoFilter->setFilter(type); +} + +JNIEXPORT void JNICALL +Java_com_cangwang_magic_util_OpenGLJniLib_setVideoFilterType(JNIEnv *env, jobject obj,jint type) { + if(glVideoFilter!= nullptr) + glVideoFilter->setFilter(type); + else ALOGE("filter set error, glVideoFilter is null"); } JNIEXPORT void JNICALL diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index 20f668e..79e9568 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -107,6 +107,11 @@ object OpenGLJniLib{ */ external fun magicVideoDraw(matrix:FloatArray) + /** + * 设置滤镜类型 + */ + external fun setVideoFilterType(type:Int) + /** * 释放视频Surface */ diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index 890c93f..64a16b5 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -1,6 +1,5 @@ package com.cangwang.magic.view - import android.annotation.SuppressLint import android.graphics.SurfaceTexture import android.media.MediaRecorder @@ -45,6 +44,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback private var previewSurface:Surface?=null private var videoEncoder:VideoEncoderCoder ?=null private var recordStatus = RECORD_IDLE + private var filterType:Int = -1 companion object { val RECORD_IDLE = 0 @@ -137,14 +137,17 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback fun drawOpenGL(){ mExecutor.execute { - mSurfaceTexture?.let{ - it.updateTexImage() + mSurfaceTexture?.apply{ + updateTexImage() if(recordStatus == RECORD_START) { if (width > 0 && height > 0) videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) videoEncoder?.apply { OpenGLJniLib.buildVideoSurface(getInputSurface(),textureId,BaseApplication.context.assets) + if(filterType>0){ + OpenGLJniLib.setVideoFilterType(filterType) + } start() isRecordVideo.set(true) } @@ -160,7 +163,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback } } - it.getTransformMatrix(mMatrix) + getTransformMatrix(mMatrix) if (isTakePhoto){ val photoAddress = getImageFileAddress() OpenGLJniLib.magicFilterDraw(mMatrix,photoAddress) @@ -168,7 +171,11 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback }else { OpenGLJniLib.magicFilterDraw(mMatrix,"") } - videoEncoder?.drainEncoder(false) + videoEncoder?.apply { + drainEncoder(false) + OpenGLJniLib.magicVideoDraw(mMatrix) + } + } } } @@ -184,6 +191,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback fun setFilterType(type:Int){ mExecutor.execute { + filterType = type OpenGLJniLib.setFilterType(type) } } From 2db522e46264168347eb2b087157af1378c60b1f Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 28 Feb 2019 19:35:32 +0800 Subject: [PATCH 09/54] =?UTF-8?q?[2019.2.28]=E4=BC=98=E5=8C=96=E5=BD=95?= =?UTF-8?q?=E5=88=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 34 +---------- app/src/main/cpp/camera/CameraFilter.h | 3 - app/src/main/cpp/egl/EGLCore.cpp | 15 +++-- app/src/main/cpp/egl/EGLCore.h | 2 + app/src/main/cpp/video/VideoFilter.cpp | 33 +++++++---- .../java/com/cangwang/magic/util/Dipatcher.kt | 59 +++++++++++++++++++ .../com/cangwang/magic/util/OpenGLJniLib.kt | 2 - .../magic/video/TextureMovieEncoder.kt | 53 ++++++++++++++++- .../view/CameraFilterSurfaceCallbackV3.kt | 25 ++------ 9 files changed, 148 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/util/Dipatcher.kt diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index f72b037..b9e3d0b 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -52,8 +52,7 @@ CameraFilter::CameraFilter(ANativeWindow *window): mWindow(window),mEGLCore(new mMatrix[15] = 1; } -CameraFilter::CameraFilter(ANativeWindow *window,AAssetManager* assetManager): mWindow(window),mEGLCore(new EGLCore()), - mVideoEGLCore(nullptr),mVideoWindow(nullptr), +CameraFilter::CameraFilter(ANativeWindow *window,AAssetManager* assetManager): mWindow(window),mEGLCore(new EGLCore()), mVideoWindow(nullptr), mAssetManager(assetManager),mTextureId(0),mTextureLoc(0), mMatrixLoc(0),filter(nullptr),cameraInputFilter(nullptr), pool(new std::MagicThreadPool()){ @@ -142,34 +141,6 @@ int CameraFilter::create() { return mTextureId; } -bool CameraFilter::buildVideoSurface(ANativeWindow *window) { - if(mVideoEGLCore == nullptr){ - mVideoWindow = window; - mVideoEGLCore = new EGLCore(); - if (!mVideoEGLCore->buildVideoContext(window,eglGetCurrentContext())){ - ALOGE("change window error"); - return false; - } else{ - return true; - } - } else{ - return false; - } -} - -void CameraFilter::releaseVideoSurface(){ - if (mVideoEGLCore){ - //清空资源 - mVideoEGLCore->release(); - delete mVideoEGLCore; - mVideoEGLCore = nullptr; - } - if (mVideoWindow){ - ANativeWindow_release(mVideoWindow); - mVideoWindow = nullptr; - } -} - void CameraFilter::change(int width, int height) { //设置视口 glViewport(0,0,width,height); @@ -207,9 +178,6 @@ void CameraFilter::draw(GLfloat *matrix) { glFlush(); if(mEGLCore!= nullptr) mEGLCore->swapBuffer(); - if(mVideoEGLCore!= nullptr){ - mVideoEGLCore->swapBuffer(); - } } } diff --git a/app/src/main/cpp/camera/CameraFilter.h b/app/src/main/cpp/camera/CameraFilter.h index 1e4587c..3aeb088 100644 --- a/app/src/main/cpp/camera/CameraFilter.h +++ b/app/src/main/cpp/camera/CameraFilter.h @@ -21,8 +21,6 @@ class CameraFilter:public GLBase{ ~CameraFilter(); void setFilter(GPUImageFilter* gpuImageFilter); int create(); - bool buildVideoSurface(ANativeWindow *window); - void releaseVideoSurface(); void draw(GLfloat *matrix); void change(int width,int height); void stop(); @@ -44,7 +42,6 @@ class CameraFilter:public GLBase{ GLint mMatrixLoc; GLfloat mMatrix[16]; EGLCore *mEGLCore; - EGLCore *mVideoEGLCore; std::MagicThreadPool *pool; }; diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index 824a90f..d91f2b9 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -158,6 +158,10 @@ GLboolean EGLCore::buildVideoContext(ANativeWindow *window, EGLContext context) return GL_FALSE; } +// if(!eglMakeCurrent(mDisplay,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)){ +// ALOGE("eglMakeCurrent reset failed: %d",eglGetError()); +// return GL_FALSE; +// } EGLint format = 0; if (!eglGetConfigAttrib(mDisplay,config,EGL_NATIVE_VISUAL_ID,&format)){ @@ -173,11 +177,6 @@ GLboolean EGLCore::buildVideoContext(ANativeWindow *window, EGLContext context) return GL_FALSE; } -// if (!eglMakeCurrent(mDisplay,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)){ -// ALOGE("free eglCurrent failed: %d",eglGetError()); -// return GL_FALSE; -// } - //把EGLContext和EGLSurface关联起来,单缓冲只使用了一个surface if (!eglMakeCurrent(mDisplay,mSurface,mSurface,mContext)){ ALOGE("eglMakeCurrent failed: %d",eglGetError()); @@ -203,6 +202,12 @@ void EGLCore::setPresentationTime(long nsecs) { eglPresentationTimeANDROID(mDisplay,mSurface,nsecs); } +void EGLCore::makeCurrent() { + if (!eglMakeCurrent(mDisplay,mSurface,mSurface,mContext)){ + ALOGE("eglMakeCurrent failed: %d",eglGetError()); + } +} + /** * 现在只使用单缓冲绘制 */ diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index f305e54..72eacb0 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -19,6 +19,8 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d GLboolean buildContext(ANativeWindow *window,EGLContext context); GLboolean buildVideoContext(ANativeWindow *window, EGLContext context); + void makeCurrent(); + void setPresentationTime(long nsecs); void swapBuffer(); diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp index e1e94e8..c2fa918 100644 --- a/app/src/main/cpp/video/VideoFilter.cpp +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -63,6 +63,10 @@ VideoFilter::VideoFilter(ANativeWindow *window,AAssetManager* assetManager): mWi mMatrix[10] = 1; mMatrix[15] = 1; + if (cameraInputFilter == nullptr){ + cameraInputFilter = new CameraInputFilter(assetManager); + } + setFilter(assetManager); } @@ -73,6 +77,13 @@ VideoFilter::~VideoFilter() { filter = nullptr; } + if (cameraInputFilter!= nullptr){ + cameraInputFilter->destroyCameraFrameBuffers(); + cameraInputFilter->destroy(); + delete cameraInputFilter; + cameraInputFilter = nullptr; + } + if (mEGLCore){ //清空资源 mEGLCore->release(); @@ -113,6 +124,11 @@ int VideoFilter::create(int textureId) { return -1; } + //相机初始化 + if (cameraInputFilter!= nullptr){ + cameraInputFilter->init(); + } + //滤镜初始化 if (filter!= nullptr) filter->init(); @@ -127,19 +143,10 @@ void VideoFilter::change(int width, int height) { glViewport(0,0,width,height); mWidth = width; mHeight = height; - if (cameraInputFilter!= nullptr){ - if (cameraInputFilter!= nullptr){ - //触发输入大小更新 - cameraInputFilter->onInputSizeChanged(width, height); - //初始化帧缓冲 - cameraInputFilter->initCameraFrameBuffer(width,height); - } - if (filter != nullptr){ - //初始化滤镜的大小 - filter->onInputSizeChanged(width,height); - } else{ - cameraInputFilter->destroyCameraFrameBuffers(); - } + + if (filter != nullptr){ + //初始化滤镜的大小 + filter->onInputSizeChanged(width,height); } } diff --git a/app/src/main/java/com/cangwang/magic/util/Dipatcher.kt b/app/src/main/java/com/cangwang/magic/util/Dipatcher.kt new file mode 100644 index 0000000..9646f09 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/util/Dipatcher.kt @@ -0,0 +1,59 @@ +package com.cangwang.magic.util + +import android.util.Log +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.lang.Exception +import java.util.concurrent.LinkedBlockingDeque +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit + +object Dipatcher{ + + fun singleDipatcher(executor: ThreadPoolExecutor):DipatchImpl{ + return DipatchImpl(executor) + } + + interface IDipatcher{ + fun dipatch(runnable:()->Unit) + fun stop(runnable: () -> Unit) + } + + class IODipatchImpl(minThreadCount:Int,maxThreadCount:Int):IDipatcher{ + + private val ioExecutors = Schedulers.from(ThreadPoolExecutor(minThreadCount,maxThreadCount,60L,TimeUnit.SECONDS,LinkedBlockingDeque())) + + override fun dipatch(runnable: () -> Unit) { + + } + + override fun stop(runnable: () -> Unit) { + + } + } + + class DipatchImpl(executor: ThreadPoolExecutor):IDipatcher{ + private var scheduler = Schedulers.from(executor) + private var mDispose:Disposable?=null + + override fun dipatch(runnable: () -> Unit) { + mDispose= Observable.just(1) + .observeOn(scheduler) + .subscribe{ + try { + runnable.invoke() + }catch (e:Exception){ + Log.e("DipatchImpl",e.toString()) + } + } + + } + + override fun stop(runnable: () -> Unit) { + if(mDispose?.isDisposed == false){ + mDispose?.dispose() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index 79e9568..d01ce5a 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -51,8 +51,6 @@ object OpenGLJniLib{ external fun magicFilterRelease() -// external fun magicFilterchangeVideoEgl(surface: Surface) - /** * 图片滤镜创建 */ diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 1d39e31..c536f1a 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -1,9 +1,56 @@ package com.cangwang.magic.video -class TextureMovieEncoder(videoEncoder: VideoEncoderCoder){ - private var videoEncoder:VideoEncoderCoder = videoEncoder +import android.os.Build +import android.os.Environment +import com.cangwang.magic.BaseApplication +import com.cangwang.magic.util.OpenGLJniLib +import java.io.File +import java.util.concurrent.Executors - fun prepareCoder(prviewWidthA:Int,preivewHeight:Int){ +class TextureMovieEncoder{ + private var videoEncoder:VideoEncoderCoder?=null + private var recoredThread = Executors.newSingleThreadExecutor() + + fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ + recoredThread.execute { + if (width > 0 && height > 0) { + videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) + videoEncoder?.apply { + OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) + + if (filterType > 0) { + OpenGLJniLib.setVideoFilterType(filterType) + } + start() + } + } + } + } + + fun stopRecord(){ + recoredThread.execute { + videoEncoder?.drainEncoder(true) + videoEncoder?.release() + videoEncoder = null + OpenGLJniLib.releaseVideoSurface() + } + } + + fun drawFrame(matrix:FloatArray,time:Long){ + recoredThread.execute { + videoEncoder?.apply { + OpenGLJniLib.magicVideoDraw(matrix) + drainEncoder(false) + } + } + } + + private fun getVideoFileAddress(): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index 64a16b5..cc7d5f0 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -12,12 +12,12 @@ import android.widget.Toast import com.cangwang.magic.BaseApplication import com.cangwang.magic.camera.CameraCompat import com.cangwang.magic.util.OpenGLJniLib +import com.cangwang.magic.video.TextureMovieEncoder import com.cangwang.magic.video.VideoEncoderCoder import io.reactivex.Observable import io.reactivex.ObservableOnSubscribe import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -import java.io.File import java.io.IOException import java.util.concurrent.Executors @@ -45,6 +45,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback private var videoEncoder:VideoEncoderCoder ?=null private var recordStatus = RECORD_IDLE private var filterType:Int = -1 + private var movieEncoder:TextureMovieEncoder = TextureMovieEncoder() companion object { val RECORD_IDLE = 0 @@ -141,23 +142,12 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback updateTexImage() if(recordStatus == RECORD_START) { - if (width > 0 && height > 0) - videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) - videoEncoder?.apply { - OpenGLJniLib.buildVideoSurface(getInputSurface(),textureId,BaseApplication.context.assets) - if(filterType>0){ - OpenGLJniLib.setVideoFilterType(filterType) - } - start() - isRecordVideo.set(true) - } + movieEncoder.startRecord(width,height,textureId,filterType) + isRecordVideo.set(true) recordStatus = RECORD_RECORDING }else if (recordStatus == RECORD_STOP){ if (isRecordVideo.get()) { - videoEncoder?.drainEncoder(true) - videoEncoder?.release() - videoEncoder = null - OpenGLJniLib.releaseVideoSurface() + movieEncoder.stopRecord() isRecordVideo.set(false) recordStatus = RECORD_IDLE } @@ -171,11 +161,8 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback }else { OpenGLJniLib.magicFilterDraw(mMatrix,"") } - videoEncoder?.apply { - drainEncoder(false) - OpenGLJniLib.magicVideoDraw(mMatrix) - } + movieEncoder.drawFrame(mMatrix,0) } } } From 1eba10505094698c8d05a0d30c3baf4a09ad7aaa Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 28 Feb 2019 20:58:14 +0800 Subject: [PATCH 10/54] =?UTF-8?q?[2019.2.28]=E6=B7=BB=E5=8A=A0=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=BD=95=E5=88=B6=E4=BB=A3=E7=A0=81=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 27 +++-------- app/src/main/cpp/camera/CameraFilter.h | 1 + app/src/main/cpp/egl/EGLCore.cpp | 17 +++---- app/src/main/cpp/egl/EGLCore.h | 2 +- app/src/main/cpp/magicjni.cpp | 20 +++++++-- app/src/main/cpp/video/VideoFilter.cpp | 45 +++++++------------ app/src/main/cpp/video/VideoFilter.h | 4 +- .../com/cangwang/magic/util/OpenGLJniLib.kt | 4 +- .../magic/video/TextureMovieEncoder.kt | 12 ++--- .../view/CameraFilterSurfaceCallbackV3.kt | 3 +- 10 files changed, 62 insertions(+), 73 deletions(-) diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index b9e3d0b..571c59b 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -21,26 +21,6 @@ * 相机滤镜管理 * cangwang 2018.12.1 */ -const static GLfloat VERTICES[]= { - -1.0f,1.0f, - 1.0f,1.0f, - -1.0f,-1.0f, - 1.0f,-1.0f -}; - -const static GLfloat TEX_COORDS[]={ - 0.0f,1.0f, - 1.0f,1.0f, - 0.0f,0.0f, - 1.0f,0.0f -}; - -const static GLuint ATTRIB_POSITION = 0; -const static GLuint ATTRIB_TEXCOORD = 1; -const static GLuint VERTEX_NUM = 4; -const static GLuint VERTEX_POS_SIZE = 2; -const static GLuint TEX_COORD_POS_SZIE = 2; - CameraFilter::CameraFilter(ANativeWindow *window): mWindow(window),mEGLCore(new EGLCore()), mAssetManager(nullptr),mTextureId(0),mTextureLoc(0), mMatrixLoc(0){ @@ -212,3 +192,10 @@ bool CameraFilter::savePhoto(std::string saveFileAddress){ return false; } +EGLContext CameraFilter::getCurrentContext() { + if (mEGLCore!= nullptr){ + return mEGLCore->getCurrent(); + } + return nullptr; +} + diff --git a/app/src/main/cpp/camera/CameraFilter.h b/app/src/main/cpp/camera/CameraFilter.h index 3aeb088..719d1fe 100644 --- a/app/src/main/cpp/camera/CameraFilter.h +++ b/app/src/main/cpp/camera/CameraFilter.h @@ -28,6 +28,7 @@ class CameraFilter:public GLBase{ void setFilter(int type); void setBeautyLevel(int level); bool savePhoto(std::string saveFileAddress); + EGLContext getCurrentContext(); protected: diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index d91f2b9..45a54ef 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -66,7 +66,7 @@ GLboolean EGLCore::buildContext(ANativeWindow *window, EGLContext context) { //只使用opengles3 GLint contextAttrib[] = {EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE}; // EGL_NO_CONTEXT表示不向其它的context共享资源 - if(context && context!= EGL_NO_CONTEXT){ + if(context){ mContext = context; } else { mContext = eglCreateContext(mDisplay, config, EGL_NO_CONTEXT, contextAttrib); @@ -184,11 +184,10 @@ GLboolean EGLCore::buildVideoContext(ANativeWindow *window, EGLContext context) } // 获取eglPresentationTimeANDROID方法的地址 -// eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROIDPROC) -// eglGetProcAddress("eglPresentationTimeANDROID"); -// if (!eglPresentationTimeANDROID) { -// ALOGE("eglPresentationTimeANDROID is not available!"); -// } + eglPresentationTimeANDROID = (EGL_PRESENTATION_TIME_ANDROIDPROC) eglGetProcAddress("eglPresentationTimeANDROID"); + if (!eglPresentationTimeANDROID) { + ALOGE("eglPresentationTimeANDROID is not available!"); + } ALOGD("buildVideoContext Succeed"); return GL_TRUE; @@ -202,10 +201,8 @@ void EGLCore::setPresentationTime(long nsecs) { eglPresentationTimeANDROID(mDisplay,mSurface,nsecs); } -void EGLCore::makeCurrent() { - if (!eglMakeCurrent(mDisplay,mSurface,mSurface,mContext)){ - ALOGE("eglMakeCurrent failed: %d",eglGetError()); - } +EGLContext EGLCore::getCurrent() { + return eglGetCurrentContext(); } /** diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index 72eacb0..88f7b0e 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -19,7 +19,7 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d GLboolean buildContext(ANativeWindow *window,EGLContext context); GLboolean buildVideoContext(ANativeWindow *window, EGLContext context); - void makeCurrent(); + EGLContext getCurrent(); void setPresentationTime(long nsecs); diff --git a/app/src/main/cpp/magicjni.cpp b/app/src/main/cpp/magicjni.cpp index fc04712..e7f76bd 100644 --- a/app/src/main/cpp/magicjni.cpp +++ b/app/src/main/cpp/magicjni.cpp @@ -187,11 +187,25 @@ Java_com_cangwang_magic_util_OpenGLJniLib_buildVideoSurface(JNIEnv *env,jobject //初始化相机采集 glVideoFilter = new VideoFilter(window,aAssetManager); //创建 - glVideoFilter->create(textureId); + if(glCameraFilter!= nullptr) + glVideoFilter->create(textureId,glCameraFilter->getCurrentContext()); +} + +//窗口大小设置,SurfaceView初始化后会触发一次 +JNIEXPORT void JNICALL +Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoFilterChange(JNIEnv *env, jobject obj,jint width,jint height) { + std::unique_lock lock(gMutex); + //视口变换,可视区域 + if (!glVideoFilter){ + ALOGE("change error, glCameraFilter is null"); + return; + } + //更改窗口大小 + glVideoFilter->change(width,height); } JNIEXPORT void JNICALL -Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoDraw(JNIEnv *env, jobject obj,jfloatArray matrix_,jstring address) { +Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoDraw(JNIEnv *env, jobject obj,jfloatArray matrix_,jlong time) { //加锁 std::unique_lock lock(gMutex); //获取摄像头矩阵 @@ -203,7 +217,7 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoDraw(JNIEnv *env, jobject ob return; } //摄像头采集画图 - glVideoFilter->draw(matrix); + glVideoFilter->draw(matrix,time); //释放矩阵数据 env->ReleaseFloatArrayElements(matrix_,matrix,0); } diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp index c2fa918..6a49ffb 100644 --- a/app/src/main/cpp/video/VideoFilter.cpp +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -21,26 +21,6 @@ * 相机滤镜管理 * cangwang 2018.12.1 */ -const static GLfloat VERTICES[]= { - -1.0f,1.0f, - 1.0f,1.0f, - -1.0f,-1.0f, - 1.0f,-1.0f -}; - -const static GLfloat TEX_COORDS[]={ - 0.0f,1.0f, - 1.0f,1.0f, - 0.0f,0.0f, - 1.0f,0.0f -}; - -const static GLuint ATTRIB_POSITION = 0; -const static GLuint ATTRIB_TEXCOORD = 1; -const static GLuint VERTEX_NUM = 4; -const static GLuint VERTEX_POS_SIZE = 2; -const static GLuint TEX_COORD_POS_SZIE = 2; - VideoFilter::VideoFilter(ANativeWindow *window): mWindow(window),mEGLCore(new EGLCore()), mAssetManager(nullptr),mTextureId(0),mTextureLoc(0), mMatrixLoc(0){ @@ -114,13 +94,13 @@ void VideoFilter::setFilter(int type) { setFilter(filter); } -int VideoFilter::create(int textureId) { +int VideoFilter::create(int textureId,EGLContext eglContext) { glDisable(GL_DITHER); glClearColor(0,0,0,0); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); - if (!mEGLCore->buildVideoContext(mWindow,eglGetCurrentContext())){ + if (!mEGLCore->buildVideoContext(mWindow,eglContext)){ return -1; } @@ -132,7 +112,7 @@ int VideoFilter::create(int textureId) { //滤镜初始化 if (filter!= nullptr) filter->init(); - mTextureId = textureId; + mTextureId = static_cast(textureId); ALOGD("get textureId success"); return mTextureId; @@ -144,19 +124,28 @@ void VideoFilter::change(int width, int height) { mWidth = width; mHeight = height; - if (filter != nullptr){ - //初始化滤镜的大小 - filter->onInputSizeChanged(width,height); + if (cameraInputFilter!= nullptr){ + if (cameraInputFilter!= nullptr){ + //触发输入大小更新 + cameraInputFilter->onInputSizeChanged(width, height); + //初始化帧缓冲 + cameraInputFilter->initCameraFrameBuffer(width,height); + } + if (filter != nullptr){ + //初始化滤镜的大小 + filter->onInputSizeChanged(width,height); + } else{ + cameraInputFilter->destroyCameraFrameBuffers(); + } } } -void VideoFilter::draw(GLfloat *matrix) { +void VideoFilter::draw(GLfloat *matrix,long time) { //清屏 glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (cameraInputFilter != nullptr){ -// cameraInputFilter->onDrawFrame(mTextureId,matrix,VERTICES,TEX_COORDS); //获取帧缓冲id GLuint id = cameraInputFilter->onDrawToTexture(mTextureId,matrix); if (filter != nullptr) diff --git a/app/src/main/cpp/video/VideoFilter.h b/app/src/main/cpp/video/VideoFilter.h index 0847075..b12db1f 100644 --- a/app/src/main/cpp/video/VideoFilter.h +++ b/app/src/main/cpp/video/VideoFilter.h @@ -20,8 +20,8 @@ class VideoFilter:public GLBase{ VideoFilter(ANativeWindow *window,AAssetManager *assetManager); ~VideoFilter(); void setFilter(GPUImageFilter* gpuImageFilter); - int create(int textureId); - void draw(GLfloat *matrix); + int create(int textureId,EGLContext eglContext); + void draw(GLfloat *matrix,long time); void change(int width,int height); void stop(); void setFilter(AAssetManager* assetManager); diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index d01ce5a..88a0cb0 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -100,10 +100,12 @@ object OpenGLJniLib{ */ external fun buildVideoSurface(surface:Surface,textureId:Int,manager: AssetManager) + external fun magicVideoFilterChange(width:Int,height:Int) + /** * 视频绘制 */ - external fun magicVideoDraw(matrix:FloatArray) + external fun magicVideoDraw(matrix:FloatArray,time:Long) /** * 设置滤镜类型 diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index c536f1a..83dc1cb 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -10,15 +10,15 @@ import java.util.concurrent.Executors class TextureMovieEncoder{ private var videoEncoder:VideoEncoderCoder?=null - private var recoredThread = Executors.newSingleThreadExecutor() + private var recordThread = Executors.newSingleThreadExecutor() fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ - recoredThread.execute { + recordThread.execute { if (width > 0 && height > 0) { videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) videoEncoder?.apply { OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) - + OpenGLJniLib.magicVideoFilterChange(width,height) if (filterType > 0) { OpenGLJniLib.setVideoFilterType(filterType) } @@ -29,7 +29,7 @@ class TextureMovieEncoder{ } fun stopRecord(){ - recoredThread.execute { + recordThread.execute { videoEncoder?.drainEncoder(true) videoEncoder?.release() videoEncoder = null @@ -38,10 +38,10 @@ class TextureMovieEncoder{ } fun drawFrame(matrix:FloatArray,time:Long){ - recoredThread.execute { + recordThread.execute { videoEncoder?.apply { - OpenGLJniLib.magicVideoDraw(matrix) drainEncoder(false) + OpenGLJniLib.magicVideoDraw(matrix,time) } } } diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index cc7d5f0..6715e33 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -76,7 +76,6 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback fun initOpenGL(surface: Surface){ mExecutor.execute { textureId = OpenGLJniLib.magicFilterCreate(surface,BaseApplication.context.assets) -// OpenGLJniLib.setFilterType(MagicFilterType.NONE.ordinal) if (textureId < 0){ Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute @@ -162,7 +161,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback OpenGLJniLib.magicFilterDraw(mMatrix,"") } - movieEncoder.drawFrame(mMatrix,0) + movieEncoder.drawFrame(mMatrix,timestamp) } } } From 864d348be4ad74516ecae955ae062a5d077b63c0 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 28 Feb 2019 22:00:47 +0800 Subject: [PATCH 11/54] =?UTF-8?q?[2019.2.28]=E4=BF=AE=E5=A4=8D=E5=BD=95?= =?UTF-8?q?=E5=88=B6=E4=B8=8A=E4=B8=8B=E6=96=87=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera/CameraFilter.cpp | 4 ---- app/src/main/cpp/egl/EGLCore.cpp | 3 ++- app/src/main/cpp/magicjni.cpp | 5 +++-- app/src/main/cpp/video/VideoFilter.cpp | 4 +++- app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt | 2 +- .../com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt | 1 + 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/cpp/camera/CameraFilter.cpp b/app/src/main/cpp/camera/CameraFilter.cpp index 571c59b..3b85684 100644 --- a/app/src/main/cpp/camera/CameraFilter.cpp +++ b/app/src/main/cpp/camera/CameraFilter.cpp @@ -80,13 +80,9 @@ CameraFilter::~CameraFilter() { } void CameraFilter::setFilter(AAssetManager* assetManager) { -// if (cameraInputFilter == nullptr){ -// cameraInputFilter = new CameraInputFilter(assetManager); -// } if(filter != nullptr){ filter->destroy(); } -// filter = new MagicAmaroFilter(assetManager); filter = new MagicNoneFilter(assetManager); filter->setPool(pool); ALOGD("set filter success"); diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index 45a54ef..cbc48b1 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -202,7 +202,8 @@ void EGLCore::setPresentationTime(long nsecs) { } EGLContext EGLCore::getCurrent() { - return eglGetCurrentContext(); + //不能使用eglGetCurrentContext,会返回为空 + return mContext; } /** diff --git a/app/src/main/cpp/magicjni.cpp b/app/src/main/cpp/magicjni.cpp index e7f76bd..b10423e 100644 --- a/app/src/main/cpp/magicjni.cpp +++ b/app/src/main/cpp/magicjni.cpp @@ -171,7 +171,7 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterRelease(JNIEnv *env, jobjec } } -JNIEXPORT void JNICALL +JNIEXPORT jint JNICALL Java_com_cangwang_magic_util_OpenGLJniLib_buildVideoSurface(JNIEnv *env,jobject obj, jobject surface,jint textureId,jobject assetManager) { std::unique_lock lock(gMutex); if(glVideoFilter){ //停止视频采集并销毁 @@ -188,7 +188,8 @@ Java_com_cangwang_magic_util_OpenGLJniLib_buildVideoSurface(JNIEnv *env,jobject glVideoFilter = new VideoFilter(window,aAssetManager); //创建 if(glCameraFilter!= nullptr) - glVideoFilter->create(textureId,glCameraFilter->getCurrentContext()); + return glVideoFilter->create(textureId,glCameraFilter->getCurrentContext()); + return -1; } //窗口大小设置,SurfaceView初始化后会触发一次 diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp index 6a49ffb..9eb5cf1 100644 --- a/app/src/main/cpp/video/VideoFilter.cpp +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -153,8 +153,10 @@ void VideoFilter::draw(GLfloat *matrix,long time) { filter->onDrawFrame(id,matrix); //缓冲区交换 glFlush(); - if(mEGLCore!= nullptr) + if(mEGLCore!= nullptr) { + mEGLCore->setPresentationTime(time); mEGLCore->swapBuffer(); + } } } diff --git a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt index 88a0cb0..5b92683 100644 --- a/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt +++ b/app/src/main/java/com/cangwang/magic/util/OpenGLJniLib.kt @@ -98,7 +98,7 @@ object OpenGLJniLib{ /** * 创建视频Surface */ - external fun buildVideoSurface(surface:Surface,textureId:Int,manager: AssetManager) + external fun buildVideoSurface(surface:Surface,textureId:Int,manager: AssetManager):Int external fun magicVideoFilterChange(width:Int,height:Int) diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index 6715e33..44e0995 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -173,6 +173,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback mSurfaceTexture=null mCamera =null } + movieEncoder.stopRecord() } fun setFilterType(type:Int){ From 05310b42293ab00568823b255e7e0455fe4e6d2e Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Fri, 1 Mar 2019 15:23:34 +0800 Subject: [PATCH 12/54] =?UTF-8?q?[2019.3.1]=E4=BF=AE=E5=A4=8D=E5=BD=95?= =?UTF-8?q?=E5=88=B6=E5=87=A0=E7=A7=92=E5=90=8E=E6=97=A0=E6=B3=95=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=9B=BE=E5=83=8F=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/video/VideoFilter.cpp | 4 +- .../magic/video/TextureMovieEncoder.kt | 25 ++- .../magic/video/TextureMovieEncoder2.kt | 181 ++++++++++++++++++ .../cangwang/magic/video/VideoEncoderCoder.kt | 74 ++++--- .../view/CameraFilterSurfaceCallbackV3.kt | 135 ++++++------- 5 files changed, 310 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp index 9eb5cf1..ec261d1 100644 --- a/app/src/main/cpp/video/VideoFilter.cpp +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -154,8 +154,10 @@ void VideoFilter::draw(GLfloat *matrix,long time) { //缓冲区交换 glFlush(); if(mEGLCore!= nullptr) { - mEGLCore->setPresentationTime(time); + //设置时间戳的方法暂时有问题,会导致录制一段时间后无法录制 +// mEGLCore->setPresentationTime(time); mEGLCore->swapBuffer(); + ALOGD("set video draw"); } } } diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 83dc1cb..da06f05 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -2,15 +2,24 @@ package com.cangwang.magic.video import android.os.Build import android.os.Environment +import android.util.Log import com.cangwang.magic.BaseApplication import com.cangwang.magic.util.OpenGLJniLib import java.io.File +import java.lang.Exception import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean class TextureMovieEncoder{ + private val TAG = "TextureMovieEncoder" private var videoEncoder:VideoEncoderCoder?=null private var recordThread = Executors.newSingleThreadExecutor() + private var isReady = AtomicBoolean() + + init { + isReady.set(false) + } fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ recordThread.execute { @@ -23,6 +32,7 @@ class TextureMovieEncoder{ OpenGLJniLib.setVideoFilterType(filterType) } start() + isReady.set(true) } } } @@ -34,14 +44,21 @@ class TextureMovieEncoder{ videoEncoder?.release() videoEncoder = null OpenGLJniLib.releaseVideoSurface() + isReady.set(false) } } fun drawFrame(matrix:FloatArray,time:Long){ - recordThread.execute { - videoEncoder?.apply { - drainEncoder(false) - OpenGLJniLib.magicVideoDraw(matrix,time) + if(isReady.get()) { + recordThread.execute { + videoEncoder?.apply { + try { + drainEncoder(false) + }catch (e:Exception){ + Log.e(TAG,e.toString()) + } + OpenGLJniLib.magicVideoDraw(matrix, time) + } } } } diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt new file mode 100644 index 0000000..98fb344 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt @@ -0,0 +1,181 @@ +package com.cangwang.magic.video + +import android.os.* +import android.util.Log +import com.cangwang.magic.BaseApplication +import com.cangwang.magic.util.OpenGLJniLib +import java.io.File +import java.lang.Exception +import java.lang.ref.WeakReference +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean + + +class TextureMovieEncoder2:Runnable{ + private val TAG = "TextureMovieEncoder" + + private var videoEncoder:VideoEncoderCoder?=null + private var recordThread = Executors.newSingleThreadExecutor() + private var isReady = AtomicBoolean() + + private val mReadyFence = java.lang.Object() // guards ready/running + private var mReady: Boolean = false + private var mRunning: Boolean = false + // ----- accessed by multiple threads ----- + @Volatile + private var mHandler: EncoderHandler? = null + + init { + isReady.set(false) + } + + fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ + Log.d(TAG, "Encoder: startRecording()") + synchronized(mReadyFence) { + if (mRunning) { + Log.w(TAG, "Encoder thread already running") + return + } + mRunning = true + Thread(this, "TextureMovieEncoder").start() + while (!mReady) { + try { + mReadyFence.wait() + } catch (ie: InterruptedException) { + // ignore + } + + } + } + mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_START_RECORDING, EncoderConfig(width,height,textureId,filterType))) + } + + fun handleStartRecord(width:Int,height:Int,textureId:Int,filterType:Int){ + if (width > 0 && height > 0) { + videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) + videoEncoder?.apply { + OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) + OpenGLJniLib.magicVideoFilterChange(width,height) + if (filterType > 0) { + OpenGLJniLib.setVideoFilterType(filterType) + } + start() + isReady.set(true) + } + } + + } + + data class EncoderConfig(val width:Int,val height:Int,val textureId:Int,val filterType:Int) + + fun stopRecord(){ + mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_STOP_RECORDING)) + mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_QUIT)) + } + + fun handleStopRecord(){ + videoEncoder?.drainEncoder(true) + videoEncoder?.release() + videoEncoder = null + OpenGLJniLib.releaseVideoSurface() + isReady.set(false) + + } + + data class DrawConfig(var matrix: FloatArray,var time: Long) + private var drawConfig:DrawConfig?=null + fun drawFrame(matrix:FloatArray,time:Long){ + synchronized(mReadyFence) { + if (!mReady) { + return + } + } + if (drawConfig==null){ + drawConfig =DrawConfig(matrix,time) + }else{ + drawConfig?.matrix = matrix + drawConfig?.time = time + } + + mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_FRAME_AVAILABLE,drawConfig)) + } + + fun handleDrawFrame(matrix:FloatArray,time:Long){ + if(isReady.get()) { + videoEncoder?.apply { + try { + drainEncoder(false) + }catch (e:Exception){ + Log.e(TAG,e.toString()) + } + OpenGLJniLib.magicVideoDraw(matrix, time) + } + + } + } + + private fun getVideoFileAddress(): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" + } + } + + override fun run() { + Looper.prepare() + synchronized(mReadyFence) { + mHandler = EncoderHandler(this) + mReady = true + mReadyFence.notify() + } + Looper.loop() + + Log.d(TAG, "Encoder thread exiting") + synchronized(mReadyFence) { + mRunning = false + mReady = mRunning + mHandler = null + } + } + + class EncoderHandler(encoder: TextureMovieEncoder2) : Handler() { + + companion object { + val TAG = "EncoderHandler" + val MSG_START_RECORDING = 0 + val MSG_STOP_RECORDING = 1 + val MSG_FRAME_AVAILABLE = 2 + val MSG_SET_TEXTURE_ID = 3 + val MSG_UPDATE_SHARED_CONTEXT = 4 + val MSG_QUIT = 5 + } + private val mWeakEncoder: WeakReference = WeakReference(encoder) + + override// runs on encoder thread + fun handleMessage(inputMessage: Message) { + val what = inputMessage.what + val obj = inputMessage.obj + + val encoder = mWeakEncoder.get() + if (encoder == null) { + Log.w(TAG, "EncoderHandler.handleMessage: encoder is null") + return + } + + when (what) { + MSG_START_RECORDING -> { + val config = obj as EncoderConfig + encoder.handleStartRecord(config.width,config.height,config.textureId,config.filterType) + } + MSG_STOP_RECORDING -> encoder.handleStopRecord() + MSG_FRAME_AVAILABLE -> { + val config = obj as DrawConfig + encoder.handleDrawFrame(config.matrix,config.time) + } + MSG_QUIT -> Looper.myLooper()!!.quit() + else -> throw RuntimeException("Unhandled msg what=$what") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index 780fac0..1cf25b6 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -5,6 +5,7 @@ import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaFormat import android.media.MediaMuxer +import android.opengl.EGL14 import android.util.Log import android.view.Surface import java.io.File @@ -67,61 +68,76 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { fun drainEncoder(endOfStream:Boolean){ val TIMEOUT_USEC = 10000L - Log.d(TAG,"drainEncoder($endOfStream)") - if (endOfStream){ - Log.d(TAG,"sending EOS to encoder") + + if (endOfStream) { + Log.d(TAG, "sending EOS to encoder") mEncoder.signalEndOfInputStream() } var encoderOutputBuffers = mEncoder.outputBuffers - while (true){ - val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo,TIMEOUT_USEC) - if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER){ - if (!endOfStream){ - break - }else{ + while (true) { + val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC) + Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus) ,eglCurrentContext(${EGL14.eglGetCurrentContext()})") + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + // no output available yet + if (!endOfStream) { + break // out of while + } else { Log.d(TAG, "no output available, spinning to await EOS") } - }else if(encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){ + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + // not expected for an encoder encoderOutputBuffers = mEncoder.outputBuffers - }else if(encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ - if (mMuxerStarted){ + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + // should happen before receiving buffers, and should only happen once + if (mMuxerStarted) { throw RuntimeException("format changed twice") } val newFormat = mEncoder.outputFormat - Log.d(TAG,"encoder output format changed: $newFormat") + Log.d(TAG, "encoder output format changed: $newFormat") + + // now that we have the Magic Goodies, start the muxer mTrackIndex = mMuxer.addTrack(newFormat) mMuxer.start() mMuxerStarted = true - }else if (encoderStatus <0){ - Log.e(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") - }else{ - val encodedData = encoderOutputBuffers[encoderStatus] ?: throw RuntimeException(("encoderOutputBuffer " + encoderStatus + - " was null")) - if ((mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){ + } else if (encoderStatus < 0) { + Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") + // let's ignore it + } else { + val encodedData = encoderOutputBuffers[encoderStatus] + ?: throw RuntimeException("encoderOutputBuffer " + encoderStatus + + " was null") + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { + // The codec config data was pulled out and fed to the muxer when we got + // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") mBufferInfo.size = 0 } - if (mBufferInfo.size!=0){ + if (mBufferInfo.size != 0) { if (!mMuxerStarted) { throw RuntimeException("muxer hasn't started") } + + // adjust the ByteBuffer values to match BufferInfo (not needed?) encodedData.position(mBufferInfo.offset) - encodedData.limit(mBufferInfo.offset+mBufferInfo.size) - mMuxer.writeSampleData(mTrackIndex,encodedData,mBufferInfo) + encodedData.limit(mBufferInfo.offset + mBufferInfo.size) + + mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo) Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + - mBufferInfo.presentationTimeUs) + mBufferInfo.presentationTimeUs) } - mEncoder.releaseOutputBuffer(encoderStatus,false) - if ((mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ - if (!endOfStream){ - Log.e(TAG, "reached end of stream unexpectedly") - }else{ + mEncoder.releaseOutputBuffer(encoderStatus, false) + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + if (!endOfStream) { + Log.w(TAG, "reached end of stream unexpectedly") + } else { Log.d(TAG, "end of stream reached") } - break + break // out of while } } } diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index 44e0995..df1c041 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -26,26 +26,26 @@ import java.util.concurrent.atomic.AtomicBoolean /** * Created by zjl on 2018/10/12. */ -class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback{ +class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callback { private val mExecutor = Executors.newSingleThreadExecutor() - private val TAG= CameraFilterSurfaceCallbackV3::class.java.simpleName!! - private var mSurfaceTexture:SurfaceTexture?=null - private var mSurface:Surface?=null - private var mCamera=camera + private val TAG = CameraFilterSurfaceCallbackV3::class.java.simpleName!! + private var mSurfaceTexture: SurfaceTexture? = null + private var mSurface: Surface? = null + private var mCamera = camera private val mMatrix = FloatArray(16) private var width = 0 private var height = 0 private var isTakePhoto = false - private var textureId:Int = -1 + private var textureId: Int = -1 - private var mMediaRecorder:MediaRecorder?=null + private var mMediaRecorder: MediaRecorder? = null private var isRecordVideo = AtomicBoolean() - private var previewSurface:Surface?=null - private var videoEncoder:VideoEncoderCoder ?=null + private var previewSurface: Surface? = null + private var videoEncoder: VideoEncoderCoder? = null private var recordStatus = RECORD_IDLE - private var filterType:Int = -1 - private var movieEncoder:TextureMovieEncoder = TextureMovieEncoder() + private var filterType: Int = -1 + private var movieEncoder: TextureMovieEncoder = TextureMovieEncoder() companion object { val RECORD_IDLE = 0 @@ -57,7 +57,7 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width this.height = height - changeOpenGL(width,height) + changeOpenGL(width, height) } override fun surfaceDestroyed(holder: SurfaceHolder?) { @@ -73,10 +73,10 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback } } - fun initOpenGL(surface: Surface){ + fun initOpenGL(surface: Surface) { mExecutor.execute { - textureId = OpenGLJniLib.magicFilterCreate(surface,BaseApplication.context.assets) - if (textureId < 0){ + textureId = OpenGLJniLib.magicFilterCreate(surface, BaseApplication.context.assets) + if (textureId < 0) { Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } @@ -87,64 +87,49 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback mCamera?.setSurfaceTexture(it) } doStartPreview() - }catch (e:IOException){ - Log.e(TAG,e.localizedMessage) + } catch (e: IOException) { + Log.e(TAG, e.localizedMessage) releaseOpenGL() } } } - fun startRecordVideo(){ + fun startRecordVideo() { recordStatus = RECORD_START } - fun stopRecordVideo(){ + fun stopRecordVideo() { videoEncoder?.stop() } - fun resumeRecordVideo(){ + fun resumeRecordVideo() { } - fun isRecording():Boolean{ + fun isRecording(): Boolean { return isRecordVideo.get() } - fun releaseRecordVideo(){ + fun releaseRecordVideo() { recordStatus = RECORD_STOP } - fun changeCamera(camera:CameraCompat? ){ - mExecutor.execute { - mCamera = camera - try { - mSurfaceTexture?.let { - mCamera?.setSurfaceTexture(it) - } - doStartPreview() - } catch (e: IOException) { - Log.e(TAG, e.localizedMessage) - releaseOpenGL() - } - } - } - - fun changeOpenGL(width:Int,height:Int){ + fun changeOpenGL(width: Int, height: Int) { mExecutor.execute { - OpenGLJniLib.magicFilterChange(width,height) + OpenGLJniLib.magicFilterChange(width, height) } } - fun drawOpenGL(){ + fun drawOpenGL() { mExecutor.execute { - mSurfaceTexture?.apply{ + mSurfaceTexture?.apply { updateTexImage() - if(recordStatus == RECORD_START) { - movieEncoder.startRecord(width,height,textureId,filterType) + if (recordStatus == RECORD_START) { + movieEncoder.startRecord(width, height, textureId, filterType) isRecordVideo.set(true) recordStatus = RECORD_RECORDING - }else if (recordStatus == RECORD_STOP){ + } else if (recordStatus == RECORD_STOP) { if (isRecordVideo.get()) { movieEncoder.stopRecord() isRecordVideo.set(false) @@ -153,38 +138,38 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback } getTransformMatrix(mMatrix) - if (isTakePhoto){ + if (isTakePhoto) { val photoAddress = getImageFileAddress() - OpenGLJniLib.magicFilterDraw(mMatrix,photoAddress) - isTakePhoto =false - }else { - OpenGLJniLib.magicFilterDraw(mMatrix,"") + OpenGLJniLib.magicFilterDraw(mMatrix, photoAddress) + isTakePhoto = false + } else { + OpenGLJniLib.magicFilterDraw(mMatrix, "") } - movieEncoder.drawFrame(mMatrix,timestamp) + movieEncoder.drawFrame(mMatrix, timestamp) } } } - fun releaseOpenGL(){ + fun releaseOpenGL() { mExecutor.execute { OpenGLJniLib.magicFilterRelease() mSurfaceTexture?.release() - mSurfaceTexture=null - mCamera =null + mSurfaceTexture = null + mCamera = null } movieEncoder.stopRecord() } - fun setFilterType(type:Int){ + fun setFilterType(type: Int) { mExecutor.execute { filterType = type OpenGLJniLib.setFilterType(type) } } - fun doStartPreview(){ - mCamera?.startPreview(object :CameraCompat.CameraStateCallBack{ + fun doStartPreview() { + mCamera?.startPreview(object : CameraCompat.CameraStateCallBack { override fun onConfigured() { } @@ -196,39 +181,39 @@ class CameraFilterSurfaceCallbackV3(camera:CameraCompat?):SurfaceHolder.Callback } @SuppressLint("CheckResult") - fun takePhoto(){ - val rootAddress= getImageFileAddress() + fun takePhoto() { + val rootAddress = getImageFileAddress() // mCamera?.stopPreview() Observable.create(ObservableOnSubscribe { it.onNext(OpenGLJniLib.savePhoto(rootAddress)) }).subscribeOn(Schedulers.from(mExecutor)) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ -// mCamera?.startPreview() - if (!it){ - Toast.makeText(BaseApplication.context,"save fail",Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(BaseApplication.context,"save success",Toast.LENGTH_SHORT).show() + // mCamera?.startPreview() + if (!it) { + Toast.makeText(BaseApplication.context, "save fail", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(BaseApplication.context, "save success", Toast.LENGTH_SHORT).show() } - },{ -// mCamera?.startPreview() - Log.e(TAG,it.toString()) + }, { + // mCamera?.startPreview() + Log.e(TAG, it.toString()) }) } fun getVideoFileAddress(): String { - return if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" + return if (Build.BRAND == "Xiaomi") { // 小米手机 + Environment.getExternalStorageDirectory().path + "/DCIM/Camera/" + System.currentTimeMillis() + ".mp4" + } else { // Meizu 、Oppo + Environment.getExternalStorageDirectory().path + "/DCIM/" + System.currentTimeMillis() + ".mp4" } } - fun getImageFileAddress():String{ - return if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + fun getImageFileAddress(): String { + return if (Build.BRAND == "Xiaomi") { // 小米手机 + Environment.getExternalStorageDirectory().path + "/DCIM/Camera/" + System.currentTimeMillis() + ".png" + } else { // Meizu 、Oppo + Environment.getExternalStorageDirectory().path + "/DCIM/" + System.currentTimeMillis() + ".png" } } } \ No newline at end of file From bd8fa6c5ea8fb406c399344583724b0572e8e62a Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 4 Mar 2019 10:53:20 +0800 Subject: [PATCH 13/54] =?UTF-8?q?[2019.3.4]=E4=BF=AE=E5=A4=8D=E5=BD=95?= =?UTF-8?q?=E5=88=B6=E4=B8=AD=E6=97=B6=E9=97=B4=E6=88=B3=E7=9A=84=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E9=97=AE=E9=A2=98=EF=BC=88java=20long=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E9=9C=80=E8=A6=81=E8=BD=AC=E5=88=B0C++=E7=9A=84long?= =?UTF-8?q?=20long=E7=B1=BB=E5=9E=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/egl/EGLCore.cpp | 2 +- app/src/main/cpp/egl/EGLCore.h | 2 +- app/src/main/cpp/video/VideoFilter.cpp | 5 ++--- app/src/main/cpp/video/VideoFilter.h | 2 +- .../java/com/cangwang/magic/video/AudioEncoderCoder.kt | 7 ++++--- .../java/com/cangwang/magic/video/TextureMovieEncoder.kt | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index cbc48b1..2b75d76 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -197,7 +197,7 @@ GLboolean EGLCore::buildVideoContext(ANativeWindow *window, EGLContext context) * 设置显示时间戳pts * @param nsecs */ -void EGLCore::setPresentationTime(long nsecs) { +void EGLCore::setPresentationTime(long long nsecs) { eglPresentationTimeANDROID(mDisplay,mSurface,nsecs); } diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index 88f7b0e..e6b92f8 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -21,7 +21,7 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d EGLContext getCurrent(); - void setPresentationTime(long nsecs); + void setPresentationTime(long long nsecs); void swapBuffer(); diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp index ec261d1..7c39218 100644 --- a/app/src/main/cpp/video/VideoFilter.cpp +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -141,7 +141,7 @@ void VideoFilter::change(int width, int height) { } -void VideoFilter::draw(GLfloat *matrix,long time) { +void VideoFilter::draw(GLfloat *matrix,long long time) { //清屏 glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -154,8 +154,7 @@ void VideoFilter::draw(GLfloat *matrix,long time) { //缓冲区交换 glFlush(); if(mEGLCore!= nullptr) { - //设置时间戳的方法暂时有问题,会导致录制一段时间后无法录制 -// mEGLCore->setPresentationTime(time); + mEGLCore->setPresentationTime(time); mEGLCore->swapBuffer(); ALOGD("set video draw"); } diff --git a/app/src/main/cpp/video/VideoFilter.h b/app/src/main/cpp/video/VideoFilter.h index b12db1f..dc2fb68 100644 --- a/app/src/main/cpp/video/VideoFilter.h +++ b/app/src/main/cpp/video/VideoFilter.h @@ -21,7 +21,7 @@ class VideoFilter:public GLBase{ ~VideoFilter(); void setFilter(GPUImageFilter* gpuImageFilter); int create(int textureId,EGLContext eglContext); - void draw(GLfloat *matrix,long time); + void draw(GLfloat *matrix,long long time); void change(int width,int height); void stop(); void setFilter(AAssetManager* assetManager); diff --git a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt index 3897ae0..917a3cc 100644 --- a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt @@ -1,11 +1,12 @@ package com.cangwang.magic.video -import android.media.MediaCodec + +import android.media.MediaFormat import android.media.MediaRecorder class AudioEncoderCoder{ private lateinit var audioEncoder:MediaRecorder.AudioEncoder - constructor(){ -// audioEncoder = MediaCodec.createEncoderByType(MediaCodec.) + init { +// val format = MediaFormat.createAudioFormat() } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index da06f05..462a368 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -15,7 +15,7 @@ class TextureMovieEncoder{ private val TAG = "TextureMovieEncoder" private var videoEncoder:VideoEncoderCoder?=null private var recordThread = Executors.newSingleThreadExecutor() - private var isReady = AtomicBoolean() + private var isReady = AtomicBoolean() init { isReady.set(false) From ef36629a5ef859b54f189c958c160e774b0027b3 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 4 Mar 2019 11:27:12 +0800 Subject: [PATCH 14/54] =?UTF-8?q?[2019.3.4]=E8=B0=83=E6=95=B4=E5=BD=95?= =?UTF-8?q?=E5=88=B6=E5=B8=A7=E7=8E=87=E5=92=8C=E5=BD=95=E5=88=B6=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/egl/EGLCore.cpp | 2 +- app/src/main/cpp/egl/EGLCore.h | 2 +- app/src/main/cpp/magicjni.cpp | 2 +- app/src/main/cpp/video/VideoFilter.cpp | 3 +-- app/src/main/cpp/video/VideoFilter.h | 2 +- .../cangwang/magic/CameraFilterV2Activity.kt | 26 +++++++++---------- .../magic/video/TextureMovieEncoder.kt | 2 +- .../cangwang/magic/video/VideoEncoderCoder.kt | 2 +- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/src/main/cpp/egl/EGLCore.cpp b/app/src/main/cpp/egl/EGLCore.cpp index 2b75d76..37cda49 100644 --- a/app/src/main/cpp/egl/EGLCore.cpp +++ b/app/src/main/cpp/egl/EGLCore.cpp @@ -197,7 +197,7 @@ GLboolean EGLCore::buildVideoContext(ANativeWindow *window, EGLContext context) * 设置显示时间戳pts * @param nsecs */ -void EGLCore::setPresentationTime(long long nsecs) { +void EGLCore::setPresentationTime(uint64_t nsecs) { eglPresentationTimeANDROID(mDisplay,mSurface,nsecs); } diff --git a/app/src/main/cpp/egl/EGLCore.h b/app/src/main/cpp/egl/EGLCore.h index e6b92f8..cae5c31 100644 --- a/app/src/main/cpp/egl/EGLCore.h +++ b/app/src/main/cpp/egl/EGLCore.h @@ -21,7 +21,7 @@ typedef EGLBoolean (EGLAPIENTRYP EGL_PRESENTATION_TIME_ANDROIDPROC)(EGLDisplay d EGLContext getCurrent(); - void setPresentationTime(long long nsecs); + void setPresentationTime(uint64_t nsecs); void swapBuffer(); diff --git a/app/src/main/cpp/magicjni.cpp b/app/src/main/cpp/magicjni.cpp index b10423e..ad869a8 100644 --- a/app/src/main/cpp/magicjni.cpp +++ b/app/src/main/cpp/magicjni.cpp @@ -218,7 +218,7 @@ Java_com_cangwang_magic_util_OpenGLJniLib_magicVideoDraw(JNIEnv *env, jobject ob return; } //摄像头采集画图 - glVideoFilter->draw(matrix,time); + glVideoFilter->draw(matrix, static_cast(time)); //释放矩阵数据 env->ReleaseFloatArrayElements(matrix_,matrix,0); } diff --git a/app/src/main/cpp/video/VideoFilter.cpp b/app/src/main/cpp/video/VideoFilter.cpp index 7c39218..3cf4832 100644 --- a/app/src/main/cpp/video/VideoFilter.cpp +++ b/app/src/main/cpp/video/VideoFilter.cpp @@ -141,7 +141,7 @@ void VideoFilter::change(int width, int height) { } -void VideoFilter::draw(GLfloat *matrix,long long time) { +void VideoFilter::draw(GLfloat *matrix,uint64_t time) { //清屏 glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -156,7 +156,6 @@ void VideoFilter::draw(GLfloat *matrix,long long time) { if(mEGLCore!= nullptr) { mEGLCore->setPresentationTime(time); mEGLCore->swapBuffer(); - ALOGD("set video draw"); } } } diff --git a/app/src/main/cpp/video/VideoFilter.h b/app/src/main/cpp/video/VideoFilter.h index dc2fb68..d53af6e 100644 --- a/app/src/main/cpp/video/VideoFilter.h +++ b/app/src/main/cpp/video/VideoFilter.h @@ -21,7 +21,7 @@ class VideoFilter:public GLBase{ ~VideoFilter(); void setFilter(GPUImageFilter* gpuImageFilter); int create(int textureId,EGLContext eglContext); - void draw(GLfloat *matrix,long long time); + void draw(GLfloat *matrix,uint64_t time); void change(int width,int height); void stop(); void setFilter(AAssetManager* assetManager); diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index bfbbc35..028f4ad 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -3,6 +3,7 @@ package com.cangwang.magic import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator +import android.animation.ValueAnimator import android.app.AlertDialog import android.content.pm.ActivityInfo import android.os.Bundle @@ -22,8 +23,6 @@ import kotlinx.android.synthetic.main.filter_layout.* * Created by cangwang on 2018/9/12. */ class CameraFilterV2Activity:AppCompatActivity(){ - - private var isRecording = false private val MODE_PIC = 1 private val MODE_VIDEO = 2 private var mode = MODE_PIC @@ -34,6 +33,8 @@ class CameraFilterV2Activity:AppCompatActivity(){ var mCamera: CameraCompat?=null + private var videoAnimator: ObjectAnimator? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // window.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) @@ -46,12 +47,12 @@ class CameraFilterV2Activity:AppCompatActivity(){ private val types = OpenGLJniLib.getFilterTypes() - fun initView(){ + private fun initView(){ filter_listView.layoutManager = LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false) mAdapter = FilterAdapter(this, types) mAdapter?.filterListener= object:FilterAdapter.onFilterChangeListener{ - override fun onFilterChanged(type: Int) { - mSurfaceCallback?.setFilterType(type) + override fun onFilterChanged(filterType: Int) { + mSurfaceCallback?.setFilterType(filterType) } } filter_listView.adapter= mAdapter @@ -95,13 +96,10 @@ class CameraFilterV2Activity:AppCompatActivity(){ .setNegativeButton("取消", null) .show() } -// val screenSize =Point() -// windowManager.defaultDisplay.getSize(screenSize) -// val params = glsurfaceview_camera.layoutParams as RelativeLayout.LayoutParams -// params.width= screenSize.x -// params.height = screenSize.x* 16/9 -// glsurfaceview_camera.layoutParams = params + videoAnimator = ObjectAnimator.ofFloat(btn_camera_shutter, "rotation", 0f, 360f) + videoAnimator?.duration = 500 + videoAnimator?.repeatCount = ValueAnimator.INFINITE } override fun onResume() { @@ -125,15 +123,17 @@ class CameraFilterV2Activity:AppCompatActivity(){ super.onDestroy() } - fun takePhoto(){ + private fun takePhoto(){ mSurfaceCallback?.takePhoto() } - fun takeVideo(){ + private fun takeVideo(){ if(mSurfaceCallback?.isRecording() == true) { mSurfaceCallback?.releaseRecordVideo() + videoAnimator?.end() }else{ mSurfaceCallback?.startRecordVideo() + videoAnimator?.start() } } diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 462a368..01e8ea4 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -24,7 +24,7 @@ class TextureMovieEncoder{ fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ recordThread.execute { if (width > 0 && height > 0) { - videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) + videoEncoder = VideoEncoderCoder(width, height, width*height*2, File(getVideoFileAddress())) videoEncoder?.apply { OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) OpenGLJniLib.magicVideoFilterChange(width,height) diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index 1cf25b6..b63628c 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -77,7 +77,7 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { var encoderOutputBuffers = mEncoder.outputBuffers while (true) { val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC) - Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus) ,eglCurrentContext(${EGL14.eglGetCurrentContext()})") + Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus)") if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { // no output available yet if (!endOfStream) { From 95e45909bbf70f3e64aa7e3b6a3c274a1459c301 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 4 Mar 2019 11:38:42 +0800 Subject: [PATCH 15/54] =?UTF-8?q?[2019.3.4]=E6=9B=BF=E6=8D=A2=E5=9B=BE?= =?UTF-8?q?=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/caches/build_file_checksums.ser | Bin 647 -> 647 bytes .idea/checkstyle-idea.xml | 1 + app/src/main/AndroidManifest.xml | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 7d26d3c0336a724bf1d1d5b6b9236e4aebb96041..f066e5d6989cb37e477961d7562cf0469db960f1 100644 GIT binary patch delta 54 zcmV-60LlM{1&0NYmj!7u1mIPXocR#521IJ$@(o{@IUpH66r>fCr2)|qs80j0i%4e( MOP|TmO|Zc5ctl$kw*UYD delta 54 zcmV-60LlM{1&0NYmj!5<*NN7VocRzWn^YfaS<_7CnB%U<&~M$7r2)|q3ekN~VJi>U MEDpHRA<5W+c+Hp^ZvX%Q diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index 19cbb50..cd86120 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -5,6 +5,7 @@ + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1efea8f..f2d5208 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ Date: Mon, 4 Mar 2019 14:55:03 +0800 Subject: [PATCH 16/54] =?UTF-8?q?[2019.3.4]=E5=A2=9E=E5=8A=A0=E7=9F=AD?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=BD=95=E5=88=B6=E9=99=90=E6=97=B6=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E8=BF=9B=E5=BA=A6=E6=9D=A1=E6=95=88=E6=9E=9C=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E5=A2=9E=E5=8A=A0=E7=A0=81=E7=8E=87=EF=BC=8C?= =?UTF-8?q?=E6=BB=A4=E9=95=9C=E8=A7=86=E9=A2=91=E6=B8=85=E6=99=B0=E5=BA=A6?= =?UTF-8?q?=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cangwang/magic/CameraFilterV2Activity.kt | 94 ++++++++++++++++++- .../magic/video/TextureMovieEncoder.kt | 5 +- .../view/CameraFilterSurfaceCallbackV3.kt | 3 +- ...video_record_seekbar_thumb_transparent.xml | 7 ++ .../video_record_seekbar_transparent.xml | 26 +++++ app/src/main/res/layout/activity_camera.xml | 21 ++++- 6 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 app/src/main/res/drawable/video_record_seekbar_thumb_transparent.xml create mode 100644 app/src/main/res/drawable/video_record_seekbar_transparent.xml diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index 028f4ad..ed18ca6 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -6,18 +6,25 @@ import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.app.AlertDialog import android.content.pm.ActivityInfo +import android.graphics.Point import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import android.view.View import android.view.Window import android.view.WindowManager +import android.widget.FrameLayout +import android.widget.RelativeLayout import com.cangwang.magic.adapter.FilterAdapter import com.cangwang.magic.camera.CameraCompat import com.cangwang.magic.util.OpenGLJniLib import com.cangwang.magic.view.CameraFilterSurfaceCallbackV3 +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable import kotlinx.android.synthetic.main.activity_camera.* import kotlinx.android.synthetic.main.filter_layout.* +import java.util.concurrent.TimeUnit /** * Created by cangwang on 2018/9/12. @@ -26,6 +33,10 @@ class CameraFilterV2Activity:AppCompatActivity(){ private val MODE_PIC = 1 private val MODE_VIDEO = 2 private var mode = MODE_PIC + /** + * 视频最长的时长是15秒 + */ + private val VIDEO_MAX_TIME = 15 private var mAdapter: FilterAdapter? = null private var mSurfaceCallback:CameraFilterSurfaceCallbackV3?=null @@ -34,6 +45,10 @@ class CameraFilterV2Activity:AppCompatActivity(){ var mCamera: CameraCompat?=null private var videoAnimator: ObjectAnimator? = null + /** + * 录像倒计时终止器 + */ + private var mDisposable: Disposable? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -115,6 +130,9 @@ class CameraFilterV2Activity:AppCompatActivity(){ } override fun onPause() { + if(mSurfaceCallback?.isRecording() == true) { + releaseVideoRecord() + } mCamera?.stopPreview(false) super.onPause() } @@ -129,11 +147,9 @@ class CameraFilterV2Activity:AppCompatActivity(){ private fun takeVideo(){ if(mSurfaceCallback?.isRecording() == true) { - mSurfaceCallback?.releaseRecordVideo() - videoAnimator?.end() + releaseVideoRecord() }else{ - mSurfaceCallback?.startRecordVideo() - videoAnimator?.start() + startVideoRecord() } } @@ -170,10 +186,78 @@ class CameraFilterV2Activity:AppCompatActivity(){ } override fun onBackPressed() { - if(layout_filter.visibility ==View.VISIBLE){ + if(layout_filter.visibility == View.VISIBLE){ hideFilters() }else { super.onBackPressed() } } + + private fun startVideoRecord(){ + mSurfaceCallback?.startRecordVideo() + videoAnimator?.start() + showVideoRecord() + } + + private fun releaseVideoRecord(){ + mSurfaceCallback?.releaseRecordVideo() + videoAnimator?.end() + hideVideoRecord() + } + + private fun showVideoRecord(){ + video_record_seek_bar.visibility = View.VISIBLE + btn_camera_mode.visibility = View.GONE + btn_camera_switch.visibility = View.GONE + btn_camera_beauty.visibility = View.GONE + btn_camera_filter.visibility = View.GONE + cutPadding() + recordCountDown() + } + + private fun hideVideoRecord(){ + video_record_seek_bar.visibility = View.GONE + btn_camera_mode.visibility = View.VISIBLE + btn_camera_switch.visibility = View.VISIBLE + btn_camera_beauty.visibility = View.VISIBLE + btn_camera_filter.visibility = View.VISIBLE + stopRecordCountTime() + } + + /** + * 重新设置录像的进度条样式 + */ + private fun cutPadding() { + val point = Point() + windowManager.defaultDisplay.getSize(point) + val width = point.x + val padding = video_record_seek_bar.paddingLeft + val layoutParams = video_record_seek_bar.layoutParams as RelativeLayout.LayoutParams + layoutParams.width = width + padding + video_record_seek_bar.layoutParams = layoutParams + video_record_seek_bar.setPadding(0, 0, 0, 0) + } + + private fun recordCountDown(){ + val count = 15 + mDisposable = Observable.interval(1, 1, TimeUnit.SECONDS) + .take((count + 1).toLong()) + .map { t -> + count-t + } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { aLong -> + val time = 16 - aLong!! + + video_record_seek_bar.progress = time.toInt() + if (time == VIDEO_MAX_TIME.toLong()) { + releaseVideoRecord() + } + } + } + + private fun stopRecordCountTime(){ + mDisposable?.dispose() + mDisposable = null + } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 01e8ea4..7f714fd 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -24,11 +24,12 @@ class TextureMovieEncoder{ fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ recordThread.execute { if (width > 0 && height > 0) { - videoEncoder = VideoEncoderCoder(width, height, width*height*2, File(getVideoFileAddress())) + //码率为4*高*宽,使用滤镜,太低码率无法记录足够的细节 + videoEncoder = VideoEncoderCoder(width, height, width*height*4, File(getVideoFileAddress())) videoEncoder?.apply { OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) OpenGLJniLib.magicVideoFilterChange(width,height) - if (filterType > 0) { + if (filterType >= 0) { OpenGLJniLib.setVideoFilterType(filterType) } start() diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index df1c041..efa9773 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -44,7 +44,7 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callb private var previewSurface: Surface? = null private var videoEncoder: VideoEncoderCoder? = null private var recordStatus = RECORD_IDLE - private var filterType: Int = -1 + private var filterType: Int = 0 private var movieEncoder: TextureMovieEncoder = TextureMovieEncoder() companion object { @@ -91,6 +91,7 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callb Log.e(TAG, e.localizedMessage) releaseOpenGL() } + OpenGLJniLib.setFilterType(filterType) } } diff --git a/app/src/main/res/drawable/video_record_seekbar_thumb_transparent.xml b/app/src/main/res/drawable/video_record_seekbar_thumb_transparent.xml new file mode 100644 index 0000000..069e3a7 --- /dev/null +++ b/app/src/main/res/drawable/video_record_seekbar_thumb_transparent.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/video_record_seekbar_transparent.xml b/app/src/main/res/drawable/video_record_seekbar_transparent.xml new file mode 100644 index 0000000..1b19600 --- /dev/null +++ b/app/src/main/res/drawable/video_record_seekbar_transparent.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml index efa07fc..ec5d13e 100644 --- a/app/src/main/res/layout/activity_camera.xml +++ b/app/src/main/res/layout/activity_camera.xml @@ -19,17 +19,30 @@ android:orientation="horizontal" android:background="@android:color/transparent"> + From 9684db15ee82ba74b5ccdbc0da1ac0414b145420 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 4 Mar 2019 16:44:58 +0800 Subject: [PATCH 17/54] =?UTF-8?q?[2019.3.4]=E6=B7=BB=E5=8A=A0=E9=9F=B3?= =?UTF-8?q?=E9=A2=91=E5=BD=95=E5=88=B6=E6=9D=83=E9=99=90=E5=92=8Cpcm?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E5=BD=95=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../java/com/cangwang/magic/MainActivity.kt | 41 ++++--- .../cangwang/magic/video/AudioEncoderCoder.kt | 30 ++++- .../cangwang/magic/video/AudioRecordCoder.kt | 109 ++++++++++++++++++ .../magic/video/TextureMovieEncoder.kt | 21 +++- .../cangwang/magic/video/VideoEncoderCoder.kt | 4 - 6 files changed, 181 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f2d5208..b3a165a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + startActivity(Intent(this, CameraActivity::class.java)) CAMERA_FILTER -> startActivity(Intent(this,CameraFilterV2Activity::class.java)) + ALBUM_REQ ->{ + PickPhotoView.Builder(this@MainActivity) + .setPickPhotoSize(1) + .setClickSelectable(true) // click one image immediately close and return image + .setShowCamera(true) + .setHasPhotoSize(7) + .setAllPhotoSize(10) + .setSpanCount(3) + .setLightStatusBar(false) + .setShowGif(false) // is show gif + .setShowVideo(false) + .start() + } else -> { } } diff --git a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt index 917a3cc..f872476 100644 --- a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt @@ -1,12 +1,36 @@ package com.cangwang.magic.video +import android.media.MediaCodec +import android.media.MediaCodecInfo import android.media.MediaFormat -import android.media.MediaRecorder class AudioEncoderCoder{ - private lateinit var audioEncoder:MediaRecorder.AudioEncoder + private lateinit var audioEncoder:MediaCodec + private val AUDIO_MIME = "audio/mp4a-latm" init { -// val format = MediaFormat.createAudioFormat() + audioEncoder = MediaCodec.createDecoderByType(AUDIO_MIME) + val format = MediaFormat() + format.setString(MediaFormat.KEY_MIME,AUDIO_MIME) + format.setInteger(MediaFormat.KEY_BIT_RATE,32000) + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT,2) + format.setInteger(MediaFormat.KEY_SAMPLE_RATE,48000) + format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, DEFAULT_BUFFER_SIZE) + format.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC) + audioEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) + } + + fun start(){ + audioEncoder.start() + } + + fun release(){ + audioEncoder.stop() + audioEncoder.release() + } + + + fun encodePCMToAAC(bytes:ByteArray){ + } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt new file mode 100644 index 0000000..0359c9a --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt @@ -0,0 +1,109 @@ +package com.cangwang.magic.video + +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaRecorder +import android.util.Log +import java.io.FileOutputStream + + +class AudioRecordCoder{ + val TAG = AudioRecordCoder::class.java.simpleName + var SAMPLE_RATE = 44100 + val CHANNEL = AudioFormat.CHANNEL_CONFIGURATION_MONO + val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT + + var audioRecorder:AudioRecord?=null + var recordThread:Thread?=null + var bufferSizeInBytes = 0 + var isRecoding = false + + private var outputStream:FileOutputStream?=null + private var outputFilePath:String?=null + + init { + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) { + Log.e(TAG,"bufferSizeInBytes not init") + } + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + + if (audioRecorder == null || audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ + SAMPLE_RATE = 16000 + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + } + if (audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ + Log.e(TAG,"audio record not init") + } + } + + fun initMeta(){ + audioRecorder?.release() + + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + + if (audioRecorder == null || audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ + SAMPLE_RATE = 16000 + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + } + } + + fun start(filePath:String){ + if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { + audioRecorder?.run { + if (state != AudioRecord.STATE_INITIALIZED) { + null + } else { + startRecording() + } + } + isRecoding = true + outputFilePath = filePath + recordThread = Thread(RecordThread(), "AudioRecordThread") + recordThread?.start() + Log.d(TAG,"audio $filePath ,start record") + } + } + + fun stop(){ + if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { + audioRecorder?.run { + recordThread?.join() + releaseAudioRecord() + isRecoding = false + } + } + } + + private fun releaseAudioRecord(){ + audioRecorder?.run { + stop() + release() + Log.d(TAG,"audio stop record") + } + } + + fun getAudioRecordBuffer(bufferSizeInBytes:Int,audioSamples:ByteArray):Int{ + audioRecorder?.run { + return read(audioSamples,0,bufferSizeInBytes) + }?:return 0 + } + + inner class RecordThread:Runnable{ + override fun run() { + outputStream = FileOutputStream(outputFilePath) + val audioSamples = ByteArray(bufferSizeInBytes) + outputStream.use { + while (isRecoding){ + val audioSampleSize = getAudioRecordBuffer(bufferSizeInBytes,audioSamples) + if (audioSampleSize != 0){ + outputStream?.write(audioSamples) + } + } + } + } + } +} diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 7f714fd..bb1e57a 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -16,16 +16,18 @@ class TextureMovieEncoder{ private var videoEncoder:VideoEncoderCoder?=null private var recordThread = Executors.newSingleThreadExecutor() private var isReady = AtomicBoolean() + private var audioEncoder:AudioRecordCoder?=null init { isReady.set(false) } fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ + val time = System.currentTimeMillis() recordThread.execute { if (width > 0 && height > 0) { //码率为4*高*宽,使用滤镜,太低码率无法记录足够的细节 - videoEncoder = VideoEncoderCoder(width, height, width*height*4, File(getVideoFileAddress())) + videoEncoder = VideoEncoderCoder(width, height, width*height*4, File(getVideoFileAddress(time))) videoEncoder?.apply { OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) OpenGLJniLib.magicVideoFilterChange(width,height) @@ -37,6 +39,8 @@ class TextureMovieEncoder{ } } } + audioEncoder = AudioRecordCoder() + audioEncoder?.start(getAudioFileAddress(time)) } fun stopRecord(){ @@ -47,6 +51,7 @@ class TextureMovieEncoder{ OpenGLJniLib.releaseVideoSurface() isReady.set(false) } + audioEncoder?.stop() } fun drawFrame(matrix:FloatArray,time:Long){ @@ -64,11 +69,19 @@ class TextureMovieEncoder{ } } - private fun getVideoFileAddress(): String { + private fun getVideoFileAddress(time:Long): String { return if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".mp4" }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" + Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".mp4" + } + } + + private fun getAudioFileAddress(time:Long): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".pcm" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".pcm" } } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index b63628c..2ff5d0a 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -49,10 +49,6 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { mEncoder.stop() } - fun resume(){ - - } - fun getInputSurface():Surface{ return mInputSurface } From 9886ce8ca692ca99c5c6f8fd0a3c22beab12051c Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 4 Mar 2019 21:46:38 +0800 Subject: [PATCH 18/54] =?UTF-8?q?[2019.3.4]=E6=B7=BB=E5=8A=A0aac=E9=9F=B3?= =?UTF-8?q?=E9=A2=91=E5=BD=95=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cangwang/magic/video/AudioEncoderCoder.kt | 101 +++++++++++++++--- .../cangwang/magic/video/AudioRecordCoder.kt | 15 ++- .../magic/video/TextureMovieEncoder.kt | 2 +- .../cangwang/magic/video/VideoEncoderCoder.kt | 59 +++++----- 4 files changed, 125 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt index f872476..8bef7e1 100644 --- a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt @@ -1,36 +1,109 @@ package com.cangwang.magic.video - import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaFormat +import android.util.Log +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException + +class AudioEncoderCoder(address:String){ + private val TAG = AudioEncoderCoder::class.java.simpleName + private var audioEncoder:MediaCodec?=null + private val SAMPLE_RATE = 44100 + private val mBufferInfo = MediaCodec.BufferInfo() + private var address: String + private var mFileStream:FileOutputStream?=null -class AudioEncoderCoder{ - private lateinit var audioEncoder:MediaCodec - private val AUDIO_MIME = "audio/mp4a-latm" init { - audioEncoder = MediaCodec.createDecoderByType(AUDIO_MIME) - val format = MediaFormat() - format.setString(MediaFormat.KEY_MIME,AUDIO_MIME) - format.setInteger(MediaFormat.KEY_BIT_RATE,32000) - format.setInteger(MediaFormat.KEY_CHANNEL_COUNT,2) - format.setInteger(MediaFormat.KEY_SAMPLE_RATE,48000) + audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC) + val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,SAMPLE_RATE,2) + format.setInteger(MediaFormat.KEY_BIT_RATE, 96000) format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, DEFAULT_BUFFER_SIZE) format.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC) - audioEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) + audioEncoder?.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) + this.address = address + mFileStream = FileOutputStream(address) } fun start(){ - audioEncoder.start() + audioEncoder?.start() } fun release(){ - audioEncoder.stop() - audioEncoder.release() + if (mFileStream !=null){ + try { + mFileStream?.flush() + mFileStream?.close() + }catch (e:IOException){ + Log.e(TAG,e.toString()) + } + } + audioEncoder?.stop() + audioEncoder?.release() + audioEncoder =null } fun encodePCMToAAC(bytes:ByteArray){ + try { + audioEncoder?.apply { + val inputBufferIndex = dequeueInputBuffer(0) + if (inputBufferIndex > 0){ + val inputBuffer = getInputBuffer(inputBufferIndex) + inputBuffer.clear() + inputBuffer.put(bytes) + audioEncoder?.queueInputBuffer(inputBufferIndex,0,bytes.size,System.nanoTime(),0) + } + + var outputBufferIndex = dequeueOutputBuffer(mBufferInfo,0) + while (outputBufferIndex>0){ + val outputBuffer = getOutputBuffer(outputBufferIndex) + val outputBufferSize = outputBuffer.limit() + 7 // ADTS头部是7个字节 + val aacBytes = ByteArray(outputBufferSize) + addADTStoPacket(aacBytes,outputBufferSize) + outputBuffer.get(aacBytes,7,outputBuffer.limit()) + mFileStream?.write(aacBytes) + + releaseOutputBuffer(outputBufferIndex,false) + outputBufferIndex = dequeueOutputBuffer(mBufferInfo,0) + } + } + }catch (e:FileNotFoundException){ + Log.e(TAG,e.toString()) + }catch (e:IOException){ + Log.e(TAG,e.toString()) + } + } + + private fun addADTStoPacket(packet: ByteArray, packetLen: Int) { + val profile = 2 //AAC LC,MediaCodecInfo.CodecProfileLevel.AACObjectLC; + val freqIdx = 4 //见后面注释avpriv_mpeg4audio_sample_rates中32000对应的数组下标,来自ffmpeg源码 + val chanCfg = 1 //见后面注释channel_configuration,AudioFormat.CHANNEL_IN_MONO 单声道(声道数量) + /*int avpriv_mpeg4audio_sample_rates[] = {96000, 88200, 64000, 48000, 44100, 32000,24000, 22050, 16000, 12000, 11025, 8000, 7350}; + channel_configuration: 表示声道数chanCfg + 0: Defined in AOT Specifc Config + 1: 1 channel: front-center + 2: 2 channels: front-left, front-right + 3: 3 channels: front-center, front-left, front-right + 4: 4 channels: front-center, front-left, front-right, back-center + 5: 5 channels: front-center, front-left, front-right, back-left, back-right + 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel + 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel + 8-15: Reserved + */ + + // fill in ADTS data + packet[0] = 0xFF.toByte() + //packet[1] = (byte)0xF9; + packet[1] = 0xF1.toByte()//解决ios 不能播放问题 + packet[2] = ((profile - 1).shl(6) + (freqIdx shl 2)+(chanCfg shr 2)).toByte() + packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte() + packet[4] = (packetLen and 0x7FF shr 3).toByte() + packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte() + packet[6] = 0xFC.toByte() } + } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt index 0359c9a..fa6d3d5 100644 --- a/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt @@ -4,21 +4,20 @@ import android.media.AudioFormat import android.media.AudioRecord import android.media.MediaRecorder import android.util.Log -import java.io.FileOutputStream class AudioRecordCoder{ val TAG = AudioRecordCoder::class.java.simpleName var SAMPLE_RATE = 44100 - val CHANNEL = AudioFormat.CHANNEL_CONFIGURATION_MONO + val CHANNEL = AudioFormat.CHANNEL_CONFIGURATION_STEREO val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT var audioRecorder:AudioRecord?=null var recordThread:Thread?=null var bufferSizeInBytes = 0 var isRecoding = false + var audioEncoder:AudioEncoderCoder?=null - private var outputStream:FileOutputStream?=null private var outputFilePath:String?=null init { @@ -60,10 +59,12 @@ class AudioRecordCoder{ startRecording() } } + audioEncoder = AudioEncoderCoder(filePath) isRecoding = true outputFilePath = filePath recordThread = Thread(RecordThread(), "AudioRecordThread") recordThread?.start() + audioEncoder?.start() Log.d(TAG,"audio $filePath ,start record") } } @@ -71,9 +72,10 @@ class AudioRecordCoder{ fun stop(){ if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { audioRecorder?.run { + isRecoding = false + audioEncoder?.release() recordThread?.join() releaseAudioRecord() - isRecoding = false } } } @@ -94,15 +96,12 @@ class AudioRecordCoder{ inner class RecordThread:Runnable{ override fun run() { - outputStream = FileOutputStream(outputFilePath) val audioSamples = ByteArray(bufferSizeInBytes) - outputStream.use { while (isRecoding){ val audioSampleSize = getAudioRecordBuffer(bufferSizeInBytes,audioSamples) if (audioSampleSize != 0){ - outputStream?.write(audioSamples) + audioEncoder?.encodePCMToAAC(audioSamples) } - } } } } diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index bb1e57a..28a3647 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -79,7 +79,7 @@ class TextureMovieEncoder{ private fun getAudioFileAddress(time:Long): String { return if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".pcm" + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".aac" }else{ // Meizu 、Oppo Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".pcm" } diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index 2ff5d0a..1c95f71 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -5,11 +5,11 @@ import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaFormat import android.media.MediaMuxer -import android.opengl.EGL14 import android.util.Log import android.view.Surface import java.io.File import java.lang.RuntimeException +import java.nio.ByteBuffer class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { companion object { @@ -25,6 +25,7 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { private var mBufferInfo:MediaCodec.BufferInfo = MediaCodec.BufferInfo() private var mTrackIndex = -1 private var mMuxerStarted = false + var encoderOutputBuffers:Array?=null init { val format = MediaFormat.createVideoFormat(MINE_TYPE,width,height) @@ -70,7 +71,6 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { mEncoder.signalEndOfInputStream() } - var encoderOutputBuffers = mEncoder.outputBuffers while (true) { val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC) Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus)") @@ -100,40 +100,41 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") // let's ignore it } else { - val encodedData = encoderOutputBuffers[encoderStatus] - ?: throw RuntimeException("encoderOutputBuffer " + encoderStatus + - " was null") - - if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { - // The codec config data was pulled out and fed to the muxer when we got - // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. - Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") - mBufferInfo.size = 0 - } - - if (mBufferInfo.size != 0) { - if (!mMuxerStarted) { - throw RuntimeException("muxer hasn't started") + encoderOutputBuffers = mEncoder.outputBuffers + if (encoderOutputBuffers != null) { + val encodedData = encoderOutputBuffers!![encoderStatus] + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { + // The codec config data was pulled out and fed to the muxer when we got + // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. + Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") + mBufferInfo.size = 0 } - // adjust the ByteBuffer values to match BufferInfo (not needed?) - encodedData.position(mBufferInfo.offset) - encodedData.limit(mBufferInfo.offset + mBufferInfo.size) + if (mBufferInfo.size != 0) { + if (!mMuxerStarted) { + throw RuntimeException("muxer hasn't started") + } + + // adjust the ByteBuffer values to match BufferInfo (not needed?) + encodedData.position(mBufferInfo.offset) + encodedData.limit(mBufferInfo.offset + mBufferInfo.size) - mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo) - Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + + mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo) + Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + mBufferInfo.presentationTimeUs) - } + } - mEncoder.releaseOutputBuffer(encoderStatus, false) + mEncoder.releaseOutputBuffer(encoderStatus, false) - if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { - if (!endOfStream) { - Log.w(TAG, "reached end of stream unexpectedly") - } else { - Log.d(TAG, "end of stream reached") + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + if (!endOfStream) { + Log.w(TAG, "reached end of stream unexpectedly") + } else { + Log.d(TAG, "end of stream reached") + } + break // out of while } - break // out of while } } } From 505815aaca01f52aabc5349cbe3561f87915a962 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Wed, 6 Mar 2019 20:49:03 +0800 Subject: [PATCH 19/54] =?UTF-8?q?[2019.3.6]=E6=8F=90=E4=BA=A4MP4=E7=9F=AD?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=BD=95=E5=88=B6=E5=90=88=E6=88=90=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cangwang/magic/video/AudioEncoderCoder.kt | 111 ++++++++--- .../magic/video/AudioEncoderCoderAAC.kt | 113 +++++++++++ .../cangwang/magic/video/AudioRecordCoder.kt | 39 ++-- .../magic/video/AudioRecordCoderAAC.kt | 111 +++++++++++ .../cangwang/magic/video/MediaMuxerCoder.kt | 117 +++++++++++ .../magic/video/TextureMovieEncoder.kt | 121 +++++++++--- .../magic/video/TextureMovieEncoder2.kt | 181 ------------------ .../magic/video/TextureMovieEncoderSource.kt | 90 +++++++++ .../cangwang/magic/video/VideoEncoderCoder.kt | 55 +++--- .../magic/video/VideoEncoderCoderMP4.kt | 145 ++++++++++++++ .../view/CameraFilterSurfaceCallbackV2.kt | 6 +- 11 files changed, 807 insertions(+), 282 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/video/AudioEncoderCoderAAC.kt create mode 100644 app/src/main/java/com/cangwang/magic/video/AudioRecordCoderAAC.kt create mode 100644 app/src/main/java/com/cangwang/magic/video/MediaMuxerCoder.kt delete mode 100644 app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt create mode 100644 app/src/main/java/com/cangwang/magic/video/TextureMovieEncoderSource.kt create mode 100644 app/src/main/java/com/cangwang/magic/video/VideoEncoderCoderMP4.kt diff --git a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt index 8bef7e1..00f3324 100644 --- a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoder.kt @@ -5,16 +5,19 @@ import android.media.MediaCodecInfo import android.media.MediaFormat import android.util.Log import java.io.FileNotFoundException -import java.io.FileOutputStream import java.io.IOException +import java.nio.ByteBuffer -class AudioEncoderCoder(address:String){ +/** + * Created by cangwang on 2019.3.6 + * 录制完整mp4短视频音轨 + */ +class AudioEncoderCoder(muxer:MediaMuxerCoder?){ private val TAG = AudioEncoderCoder::class.java.simpleName private var audioEncoder:MediaCodec?=null private val SAMPLE_RATE = 44100 private val mBufferInfo = MediaCodec.BufferInfo() - private var address: String - private var mFileStream:FileOutputStream?=null + private var mMuxer:MediaMuxerCoder?=null init { audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC) @@ -23,8 +26,7 @@ class AudioEncoderCoder(address:String){ format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, DEFAULT_BUFFER_SIZE) format.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC) audioEncoder?.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) - this.address = address - mFileStream = FileOutputStream(address) + mMuxer=muxer } fun start(){ @@ -32,20 +34,21 @@ class AudioEncoderCoder(address:String){ } fun release(){ - if (mFileStream !=null){ - try { - mFileStream?.flush() - mFileStream?.close() - }catch (e:IOException){ - Log.e(TAG,e.toString()) - } - } audioEncoder?.stop() audioEncoder?.release() - audioEncoder =null + audioEncoder = null } + var prevPresentationTimes = mBufferInfo.presentationTimeUs + private fun getPTSUs(): Long { + var result = System.nanoTime() / 1000 + if (result < prevPresentationTimes) { + result += prevPresentationTimes - result + } + return result + } + var encoderOutputBuffers:Array?=null fun encodePCMToAAC(bytes:ByteArray){ try { audioEncoder?.apply { @@ -54,20 +57,74 @@ class AudioEncoderCoder(address:String){ val inputBuffer = getInputBuffer(inputBufferIndex) inputBuffer.clear() inputBuffer.put(bytes) - audioEncoder?.queueInputBuffer(inputBufferIndex,0,bytes.size,System.nanoTime(),0) + audioEncoder?.queueInputBuffer(inputBufferIndex,0,bytes.size,getPTSUs(),0) } - var outputBufferIndex = dequeueOutputBuffer(mBufferInfo,0) - while (outputBufferIndex>0){ - val outputBuffer = getOutputBuffer(outputBufferIndex) - val outputBufferSize = outputBuffer.limit() + 7 // ADTS头部是7个字节 - val aacBytes = ByteArray(outputBufferSize) - addADTStoPacket(aacBytes,outputBufferSize) - outputBuffer.get(aacBytes,7,outputBuffer.limit()) - mFileStream?.write(aacBytes) - - releaseOutputBuffer(outputBufferIndex,false) - outputBufferIndex = dequeueOutputBuffer(mBufferInfo,0) + while (true) { + val encoderStatus = dequeueOutputBuffer(mBufferInfo, 0) + + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + Log.d(TAG, "no output available, spinning to await EOS") + break + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + // not expected for an encoder + encoderOutputBuffers = outputBuffers + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + //一定要在这个节点上获取fomart设置到混合器的音轨上 + // should happen before receiving buffers, and should only happen once + if (mMuxer?.hasAudioTrack() == true) { + Log.d(TAG,"audio format changed twice") + break + } + val newFormat = outputFormat + Log.d(TAG, "encoder output format changed: $newFormat") + + // now that we have the Magic Goodies, start the muxer + val track = mMuxer?.setAudioTrack(newFormat) + Log.d(TAG, "audiotrack: $track") + mMuxer?.start() + } else if (encoderStatus < 0) { + Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") + // let's ignore it + } else { + encoderOutputBuffers = outputBuffers + if (encoderOutputBuffers != null) { + val encodedData = encoderOutputBuffers!![encoderStatus] + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { + // The codec config data was pulled out and fed to the muxer when we got + // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. + Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") + mBufferInfo.size = 0 + } + + if (mBufferInfo.size != 0) { + if (mMuxer?.isRecording() == false) { + Log.d(TAG,"muxer hasn't started") + //释放采集的音轨数据 + releaseOutputBuffer(encoderStatus, false) + break +// throw RuntimeException("muxer hasn't started") + } + + val outputBufferSize = encodedData.limit() + 7 // ADTS头部是7个字节 + val aacBytes = ByteArray(outputBufferSize) + addADTStoPacket(aacBytes,outputBufferSize) + encodedData.get(aacBytes,7,encodedData.limit()) + mBufferInfo.presentationTimeUs = getPTSUs() + mMuxer?.writeAudioData(encodedData,mBufferInfo) + +// Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + mBufferInfo.presentationTimeUs) + } + //释放采集的音轨数据 + releaseOutputBuffer(encoderStatus, false) + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + Log.d(TAG, "end of stream reached") + break // out of while + } + } + } } } }catch (e:FileNotFoundException){ diff --git a/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoderAAC.kt b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoderAAC.kt new file mode 100644 index 0000000..d1314d0 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/AudioEncoderCoderAAC.kt @@ -0,0 +1,113 @@ +package com.cangwang.magic.video + +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaFormat +import android.util.Log +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException + +/** + * Created by cangwang on 2019.3.6 + * 单独AAC音频录制 + */ +class AudioEncoderCoderAAC(address:String){ + private val TAG = AudioEncoderCoder::class.java.simpleName + private var audioEncoder:MediaCodec?=null + private val SAMPLE_RATE = 44100 + private val mBufferInfo = MediaCodec.BufferInfo() + private var address: String + private var mFileStream:FileOutputStream?=null + + init { + audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC) + val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,SAMPLE_RATE,2) + format.setInteger(MediaFormat.KEY_BIT_RATE, 96000) + format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, DEFAULT_BUFFER_SIZE) + format.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC) + audioEncoder?.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) + this.address = address + mFileStream = FileOutputStream(address) + } + + fun start(){ + audioEncoder?.start() + } + + fun release(){ + if (mFileStream !=null){ + try { + mFileStream?.flush() + mFileStream?.close() + }catch (e:IOException){ + Log.e(TAG,e.toString()) + } + } + audioEncoder?.stop() + audioEncoder?.release() + audioEncoder =null + } + + + fun encodePCMToAAC(bytes:ByteArray){ + try { + audioEncoder?.apply { + val inputBufferIndex = dequeueInputBuffer(0) + if (inputBufferIndex > 0){ + val inputBuffer = getInputBuffer(inputBufferIndex) + inputBuffer.clear() + inputBuffer.put(bytes) + audioEncoder?.queueInputBuffer(inputBufferIndex,0,bytes.size,System.nanoTime(),0) + } + + var outputBufferIndex = dequeueOutputBuffer(mBufferInfo,0) + while (outputBufferIndex>0){ + val outputBuffer = getOutputBuffer(outputBufferIndex) + val outputBufferSize = outputBuffer.limit() + 7 // ADTS头部是7个字节 + val aacBytes = ByteArray(outputBufferSize) + addADTStoPacket(aacBytes,outputBufferSize) + outputBuffer.get(aacBytes,7,outputBuffer.limit()) + mFileStream?.write(aacBytes) + + releaseOutputBuffer(outputBufferIndex,false) + outputBufferIndex = dequeueOutputBuffer(mBufferInfo,0) + } + } + }catch (e:FileNotFoundException){ + Log.e(TAG,e.toString()) + }catch (e:IOException){ + Log.e(TAG,e.toString()) + } + } + + private fun addADTStoPacket(packet: ByteArray, packetLen: Int) { + val profile = 2 //AAC LC,MediaCodecInfo.CodecProfileLevel.AACObjectLC; + val freqIdx = 4 //见后面注释avpriv_mpeg4audio_sample_rates中32000对应的数组下标,来自ffmpeg源码 + val chanCfg = 1 //见后面注释channel_configuration,AudioFormat.CHANNEL_IN_MONO 单声道(声道数量) + + /*int avpriv_mpeg4audio_sample_rates[] = {96000, 88200, 64000, 48000, 44100, 32000,24000, 22050, 16000, 12000, 11025, 8000, 7350}; + channel_configuration: 表示声道数chanCfg + 0: Defined in AOT Specifc Config + 1: 1 channel: front-center + 2: 2 channels: front-left, front-right + 3: 3 channels: front-center, front-left, front-right + 4: 4 channels: front-center, front-left, front-right, back-center + 5: 5 channels: front-center, front-left, front-right, back-left, back-right + 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel + 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel + 8-15: Reserved + */ + + // fill in ADTS data + packet[0] = 0xFF.toByte() + //packet[1] = (byte)0xF9; + packet[1] = 0xF1.toByte()//解决ios 不能播放问题 + packet[2] = ((profile - 1).shl(6) + (freqIdx shl 2)+(chanCfg shr 2)).toByte() + packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte() + packet[4] = (packetLen and 0x7FF shr 3).toByte() + packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte() + packet[6] = 0xFC.toByte() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt index fa6d3d5..c69df5b 100644 --- a/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoder.kt @@ -4,8 +4,12 @@ import android.media.AudioFormat import android.media.AudioRecord import android.media.MediaRecorder import android.util.Log +import java.util.concurrent.atomic.AtomicBoolean - +/** + * Created by cangwang on 2019.3.6 + * 录制完整mp4短视频音轨 + */ class AudioRecordCoder{ val TAG = AudioRecordCoder::class.java.simpleName var SAMPLE_RATE = 44100 @@ -15,10 +19,10 @@ class AudioRecordCoder{ var audioRecorder:AudioRecord?=null var recordThread:Thread?=null var bufferSizeInBytes = 0 - var isRecoding = false + var isRecoding = AtomicBoolean(false) var audioEncoder:AudioEncoderCoder?=null - private var outputFilePath:String?=null + private var muxerCoder:MediaMuxerCoder?=null init { bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) @@ -35,23 +39,12 @@ class AudioRecordCoder{ if (audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ Log.e(TAG,"audio record not init") } - } - fun initMeta(){ - audioRecorder?.release() - - bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) - audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) - - if (audioRecorder == null || audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ - SAMPLE_RATE = 16000 - bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) - audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) - } } - fun start(filePath:String){ + fun start(muxer: MediaMuxerCoder?){ if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { + muxerCoder = muxer audioRecorder?.run { if (state != AudioRecord.STATE_INITIALIZED) { null @@ -59,22 +52,22 @@ class AudioRecordCoder{ startRecording() } } - audioEncoder = AudioEncoderCoder(filePath) - isRecoding = true - outputFilePath = filePath + isRecoding.set(true) + audioEncoder = AudioEncoderCoder(muxerCoder) recordThread = Thread(RecordThread(), "AudioRecordThread") recordThread?.start() audioEncoder?.start() - Log.d(TAG,"audio $filePath ,start record") } } fun stop(){ if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { audioRecorder?.run { - isRecoding = false - audioEncoder?.release() + Log.d(TAG,"stop audio") + isRecoding.set(false) recordThread?.join() + Log.d(TAG,"release") + audioEncoder?.release() releaseAudioRecord() } } @@ -97,7 +90,7 @@ class AudioRecordCoder{ inner class RecordThread:Runnable{ override fun run() { val audioSamples = ByteArray(bufferSizeInBytes) - while (isRecoding){ + while (isRecoding.get()){ //循环采集音轨 val audioSampleSize = getAudioRecordBuffer(bufferSizeInBytes,audioSamples) if (audioSampleSize != 0){ audioEncoder?.encodePCMToAAC(audioSamples) diff --git a/app/src/main/java/com/cangwang/magic/video/AudioRecordCoderAAC.kt b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoderAAC.kt new file mode 100644 index 0000000..edaa6d7 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/AudioRecordCoderAAC.kt @@ -0,0 +1,111 @@ +package com.cangwang.magic.video + +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaRecorder +import android.util.Log + +/** + * Created by cangwang on 2019.3.6 + * 单独AAC音频录制 + */ +class AudioRecordCoderAAC{ + val TAG = AudioRecordCoder::class.java.simpleName + var SAMPLE_RATE = 44100 + val CHANNEL = AudioFormat.CHANNEL_CONFIGURATION_STEREO + val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT + + var audioRecorder:AudioRecord?=null + var recordThread:Thread?=null + var bufferSizeInBytes = 0 + var isRecoding = false + var audioEncoder:AudioEncoderCoderAAC?=null + + private var outputFilePath:String?=null + + init { + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) { + Log.e(TAG,"bufferSizeInBytes not init") + } + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + + if (audioRecorder == null || audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ + SAMPLE_RATE = 16000 + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + } + if (audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ + Log.e(TAG,"audio record not init") + } + } + + fun initMeta(){ + audioRecorder?.release() + + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + + if (audioRecorder == null || audioRecorder!!.state !=AudioRecord.STATE_INITIALIZED){ + SAMPLE_RATE = 16000 + bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE,CHANNEL,AUDIO_FORMAT) + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE,CHANNEL,AUDIO_FORMAT,bufferSizeInBytes) + } + } + + fun start(filePath:String){ + if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { + audioRecorder?.run { + if (state != AudioRecord.STATE_INITIALIZED) { + null + } else { + startRecording() + } + } + audioEncoder = AudioEncoderCoderAAC(filePath) + isRecoding = true + outputFilePath = filePath + recordThread = Thread(RecordThread(), "AudioRecordThread") + recordThread?.start() + audioEncoder?.start() + Log.d(TAG,"audio $filePath ,start record") + } + } + + fun stop(){ + if(audioRecorder?.state == AudioRecord.STATE_INITIALIZED) { + audioRecorder?.run { + isRecoding = false + audioEncoder?.release() + recordThread?.join() + releaseAudioRecord() + } + } + } + + private fun releaseAudioRecord(){ + audioRecorder?.run { + stop() + release() + Log.d(TAG,"audio stop record") + } + } + + fun getAudioRecordBuffer(bufferSizeInBytes:Int,audioSamples:ByteArray):Int{ + audioRecorder?.run { + return read(audioSamples,0,bufferSizeInBytes) + }?:return 0 + } + + inner class RecordThread:Runnable{ + override fun run() { + val audioSamples = ByteArray(bufferSizeInBytes) + while (isRecoding){ + val audioSampleSize = getAudioRecordBuffer(bufferSizeInBytes,audioSamples) + if (audioSampleSize != 0){ + audioEncoder?.encodePCMToAAC(audioSamples) + } + } + } + } +} diff --git a/app/src/main/java/com/cangwang/magic/video/MediaMuxerCoder.kt b/app/src/main/java/com/cangwang/magic/video/MediaMuxerCoder.kt new file mode 100644 index 0000000..9fe9290 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/MediaMuxerCoder.kt @@ -0,0 +1,117 @@ +package com.cangwang.magic.video + +import android.media.MediaCodec +import android.media.MediaFormat +import android.media.MediaMuxer +import android.util.Log +import java.io.File +import java.nio.ByteBuffer +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Created by cangwang on 2019.3.6 + * 录制完整mp4小视频混合器 + */ +class MediaMuxerCoder(outFile: File){ + private val TAG= MediaMuxerCoder::class.java.simpleName + private var mMuxer: MediaMuxer?=null + private var NONE_TRACK = -1 + private var videoTrack = NONE_TRACK + private var audioTrack = NONE_TRACK + private var mMuxerStarted= AtomicBoolean(false) + + init { + mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) + } + + /** + * 设置视轨 + */ + fun setVideoTrack(mediaFormat: MediaFormat):Int{ + videoTrack = mMuxer?.addTrack(mediaFormat)?:-1 + return videoTrack + } + + /** + * 设置音轨 + */ + fun setAudioTrack(mediaFormat: MediaFormat):Int{ + audioTrack = mMuxer?.addTrack(mediaFormat)?:-1 + return audioTrack + } + + fun reset(outFile: File){ + release() + mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) + videoTrack = NONE_TRACK + audioTrack = NONE_TRACK + mMuxerStarted.set(false) + } + + /** + * 开启混合器 + */ + fun start(){ + //等待音轨和视轨同时设置完成后开启混合器 + if (videoTrack != NONE_TRACK && audioTrack != NONE_TRACK) { + mMuxer?.start() + mMuxerStarted.set(true) + Log.d(TAG,"muxer start") + } + } + + /** + * 是否已经写入音轨 + */ + fun hasAudioTrack():Boolean{ + return audioTrack != NONE_TRACK + } + + /** + * 是否已经写入视轨 + */ + fun hasVideoTrack():Boolean{ + return videoTrack != NONE_TRACK + } + + fun isRecording():Boolean{ + return mMuxerStarted.get() + } + + /** + * 写入视频数据 + */ + fun writeVideoData(byteBuffer: ByteBuffer,bufferInfo: MediaCodec.BufferInfo){ + if (videoTrack != NONE_TRACK && mMuxerStarted.get()) { + writeData(videoTrack, byteBuffer, bufferInfo) + } + } + + /** + * 写入音频数据 + */ + fun writeAudioData(byteBuffer: ByteBuffer,bufferInfo: MediaCodec.BufferInfo){ + if (audioTrack != NONE_TRACK && mMuxerStarted.get()) { + writeData(audioTrack, byteBuffer, bufferInfo) + } + } + + /** + * 写入数据到混合器 + */ + private fun writeData(track:Int, byteBuffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo){ + synchronized(MediaMuxerCoder::class.java){ + Log.d(TAG, "track = $track, sent " + bufferInfo.size + " bytes to muxer, ts=" + bufferInfo.presentationTimeUs) + mMuxer?.writeSampleData(track, byteBuffer, bufferInfo) + } + } + + /** + * 释放混合器资源 + */ + fun release(){ + mMuxer?.stop() + mMuxer?.release() + mMuxer=null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 28a3647..6309050 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -1,33 +1,66 @@ package com.cangwang.magic.video +import android.annotation.SuppressLint import android.os.Build import android.os.Environment import android.util.Log import com.cangwang.magic.BaseApplication import com.cangwang.magic.util.OpenGLJniLib +import io.reactivex.Observable +import io.reactivex.ObservableOnSubscribe +import io.reactivex.functions.BiFunction +import io.reactivex.schedulers.Schedulers import java.io.File import java.lang.Exception import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean - +/** + * Created by cangwang on 2019.3.6 + * 录制完整mp4短视频 + */ class TextureMovieEncoder{ private val TAG = "TextureMovieEncoder" private var videoEncoder:VideoEncoderCoder?=null - private var recordThread = Executors.newSingleThreadExecutor() + private var videoThread = Executors.newSingleThreadExecutor() + private var audioThread = Executors.newSingleThreadExecutor() private var isReady = AtomicBoolean() private var audioEncoder:AudioRecordCoder?=null + private var muxerEncoder:MediaMuxerCoder?=null init { isReady.set(false) } - fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ - val time = System.currentTimeMillis() - recordThread.execute { + /** + * 开始采集 + */ + @SuppressLint("CheckResult") + fun startRecord(width:Int, height:Int, textureId:Int, filterType:Int){ + //混合器 + muxerEncoder = MediaMuxerCoder(File(getVideoFileAddress(System.currentTimeMillis()))) + //开启视频和音频采集 + Observable.zip(startVideo(width, height, textureId, filterType),startAudio(), + object: BiFunction{ + override fun apply(isVideoReady: Boolean, isAudioReady: Boolean): Boolean { + return isVideoReady && isAudioReady + } + }).observeOn(Schedulers.io()) + .subscribe ({ + isReady.set(true) + },{ + Log.e(TAG,"init error $it") + }) + } + + /** + * 视频采集 + */ + private fun startVideo(width:Int, height:Int, textureId:Int, filterType:Int):Observable{ + return Observable.create(ObservableOnSubscribe { if (width > 0 && height > 0) { //码率为4*高*宽,使用滤镜,太低码率无法记录足够的细节 - videoEncoder = VideoEncoderCoder(width, height, width*height*4, File(getVideoFileAddress(time))) + videoEncoder = VideoEncoderCoder(width, height, width*height*4, muxerEncoder) videoEncoder?.apply { OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) OpenGLJniLib.magicVideoFilterChange(width,height) @@ -35,40 +68,90 @@ class TextureMovieEncoder{ OpenGLJniLib.setVideoFilterType(filterType) } start() - isReady.set(true) + } } - } - audioEncoder = AudioRecordCoder() - audioEncoder?.start(getAudioFileAddress(time)) + it.onNext(true) + }).subscribeOn(Schedulers.from(videoThread)) + } + + /** + * 音频采集 + */ + private fun startAudio():Observable{ + return Observable.create(ObservableOnSubscribe { + audioEncoder = AudioRecordCoder() + audioEncoder?.start(muxerEncoder) + it.onNext(true) + }).subscribeOn(Schedulers.from(audioThread)) } + /** + * 停止录制 + */ + @SuppressLint("CheckResult") fun stopRecord(){ - recordThread.execute { + Observable.zip(stopVideo(),stopAudio(), + object: BiFunction{ + override fun apply(isVideoStop: Boolean, isAudioStop: Boolean): Boolean { + return isVideoStop && isAudioStop + } + }).observeOn(Schedulers.io()) + .subscribe ({ + muxerEncoder?.release() + Log.d(TAG,"muxer release") + },{ + Log.e(TAG,it.toString()) + }) + } + + /** + * 停止视频录制 + */ + private fun stopVideo():Observable{ + return Observable.create(ObservableOnSubscribe { + isReady.set(false) videoEncoder?.drainEncoder(true) videoEncoder?.release() videoEncoder = null OpenGLJniLib.releaseVideoSurface() - isReady.set(false) - } - audioEncoder?.stop() + it.onNext(true) + }).subscribeOn(Schedulers.from(videoThread)) + } + + /** + * 停止音频录制 + */ + private fun stopAudio():Observable{ + return Observable.create(ObservableOnSubscribe { + audioEncoder?.stop() + it.onNext(true) + }).subscribeOn(Schedulers.from(audioThread)) } + /** + * 视频Surface绘制 + */ fun drawFrame(matrix:FloatArray,time:Long){ if(isReady.get()) { - recordThread.execute { + videoThread.execute { videoEncoder?.apply { try { + //传输视频帧数据 drainEncoder(false) }catch (e:Exception){ Log.e(TAG,e.toString()) } + //绘制Suface OpenGLJniLib.magicVideoDraw(matrix, time) } } } } + /** + * 获取文件地址 + */ private fun getVideoFileAddress(time:Long): String { return if(Build.BRAND == "Xiaomi"){ // 小米手机 Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".mp4" @@ -76,12 +159,4 @@ class TextureMovieEncoder{ Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".mp4" } } - - private fun getAudioFileAddress(time:Long): String { - return if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".aac" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".pcm" - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt deleted file mode 100644 index 98fb344..0000000 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder2.kt +++ /dev/null @@ -1,181 +0,0 @@ -package com.cangwang.magic.video - -import android.os.* -import android.util.Log -import com.cangwang.magic.BaseApplication -import com.cangwang.magic.util.OpenGLJniLib -import java.io.File -import java.lang.Exception -import java.lang.ref.WeakReference -import java.util.concurrent.Executors -import java.util.concurrent.atomic.AtomicBoolean - - -class TextureMovieEncoder2:Runnable{ - private val TAG = "TextureMovieEncoder" - - private var videoEncoder:VideoEncoderCoder?=null - private var recordThread = Executors.newSingleThreadExecutor() - private var isReady = AtomicBoolean() - - private val mReadyFence = java.lang.Object() // guards ready/running - private var mReady: Boolean = false - private var mRunning: Boolean = false - // ----- accessed by multiple threads ----- - @Volatile - private var mHandler: EncoderHandler? = null - - init { - isReady.set(false) - } - - fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ - Log.d(TAG, "Encoder: startRecording()") - synchronized(mReadyFence) { - if (mRunning) { - Log.w(TAG, "Encoder thread already running") - return - } - mRunning = true - Thread(this, "TextureMovieEncoder").start() - while (!mReady) { - try { - mReadyFence.wait() - } catch (ie: InterruptedException) { - // ignore - } - - } - } - mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_START_RECORDING, EncoderConfig(width,height,textureId,filterType))) - } - - fun handleStartRecord(width:Int,height:Int,textureId:Int,filterType:Int){ - if (width > 0 && height > 0) { - videoEncoder = VideoEncoderCoder(width, height, 1000000, File(getVideoFileAddress())) - videoEncoder?.apply { - OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) - OpenGLJniLib.magicVideoFilterChange(width,height) - if (filterType > 0) { - OpenGLJniLib.setVideoFilterType(filterType) - } - start() - isReady.set(true) - } - } - - } - - data class EncoderConfig(val width:Int,val height:Int,val textureId:Int,val filterType:Int) - - fun stopRecord(){ - mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_STOP_RECORDING)) - mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_QUIT)) - } - - fun handleStopRecord(){ - videoEncoder?.drainEncoder(true) - videoEncoder?.release() - videoEncoder = null - OpenGLJniLib.releaseVideoSurface() - isReady.set(false) - - } - - data class DrawConfig(var matrix: FloatArray,var time: Long) - private var drawConfig:DrawConfig?=null - fun drawFrame(matrix:FloatArray,time:Long){ - synchronized(mReadyFence) { - if (!mReady) { - return - } - } - if (drawConfig==null){ - drawConfig =DrawConfig(matrix,time) - }else{ - drawConfig?.matrix = matrix - drawConfig?.time = time - } - - mHandler?.sendMessage(mHandler?.obtainMessage(EncoderHandler.MSG_FRAME_AVAILABLE,drawConfig)) - } - - fun handleDrawFrame(matrix:FloatArray,time:Long){ - if(isReady.get()) { - videoEncoder?.apply { - try { - drainEncoder(false) - }catch (e:Exception){ - Log.e(TAG,e.toString()) - } - OpenGLJniLib.magicVideoDraw(matrix, time) - } - - } - } - - private fun getVideoFileAddress(): String { - return if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".mp4" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".mp4" - } - } - - override fun run() { - Looper.prepare() - synchronized(mReadyFence) { - mHandler = EncoderHandler(this) - mReady = true - mReadyFence.notify() - } - Looper.loop() - - Log.d(TAG, "Encoder thread exiting") - synchronized(mReadyFence) { - mRunning = false - mReady = mRunning - mHandler = null - } - } - - class EncoderHandler(encoder: TextureMovieEncoder2) : Handler() { - - companion object { - val TAG = "EncoderHandler" - val MSG_START_RECORDING = 0 - val MSG_STOP_RECORDING = 1 - val MSG_FRAME_AVAILABLE = 2 - val MSG_SET_TEXTURE_ID = 3 - val MSG_UPDATE_SHARED_CONTEXT = 4 - val MSG_QUIT = 5 - } - private val mWeakEncoder: WeakReference = WeakReference(encoder) - - override// runs on encoder thread - fun handleMessage(inputMessage: Message) { - val what = inputMessage.what - val obj = inputMessage.obj - - val encoder = mWeakEncoder.get() - if (encoder == null) { - Log.w(TAG, "EncoderHandler.handleMessage: encoder is null") - return - } - - when (what) { - MSG_START_RECORDING -> { - val config = obj as EncoderConfig - encoder.handleStartRecord(config.width,config.height,config.textureId,config.filterType) - } - MSG_STOP_RECORDING -> encoder.handleStopRecord() - MSG_FRAME_AVAILABLE -> { - val config = obj as DrawConfig - encoder.handleDrawFrame(config.matrix,config.time) - } - MSG_QUIT -> Looper.myLooper()!!.quit() - else -> throw RuntimeException("Unhandled msg what=$what") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoderSource.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoderSource.kt new file mode 100644 index 0000000..8cb4769 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoderSource.kt @@ -0,0 +1,90 @@ +package com.cangwang.magic.video + +import android.os.Build +import android.os.Environment +import android.util.Log +import com.cangwang.magic.BaseApplication +import com.cangwang.magic.util.OpenGLJniLib +import java.io.File +import java.lang.Exception +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Created by cangwang on 2019.3.6 + * 分别录制aac音频和mp4视频 + */ +class TextureMovieEncoderSource{ + private val TAG = "TextureMovieEncoder" + private var videoEncoder:VideoEncoderCoderMP4?=null + private var recordThread = Executors.newSingleThreadExecutor() + private var isReady = AtomicBoolean() + private var audioEncoder:AudioRecordCoderAAC?=null + + init { + isReady.set(false) + } + + fun startRecord(width:Int,height:Int,textureId:Int,filterType:Int){ + val time = System.currentTimeMillis() + recordThread.execute { + if (width > 0 && height > 0) { + //码率为4*高*宽,使用滤镜,太低码率无法记录足够的细节 + videoEncoder = VideoEncoderCoderMP4(width, height, width*height*4, File(getVideoFileAddress(time))) + videoEncoder?.apply { + OpenGLJniLib.buildVideoSurface(getInputSurface(), textureId, BaseApplication.context.assets) + OpenGLJniLib.magicVideoFilterChange(width,height) + if (filterType >= 0) { + OpenGLJniLib.setVideoFilterType(filterType) + } + start() + isReady.set(true) + } + } + } + audioEncoder = AudioRecordCoderAAC() + audioEncoder?.start(getAudioFileAddress(time)) + } + + fun stopRecord(){ + recordThread.execute { + videoEncoder?.drainEncoder(true) + videoEncoder?.release() + videoEncoder = null + OpenGLJniLib.releaseVideoSurface() + isReady.set(false) + } + audioEncoder?.stop() + } + + fun drawFrame(matrix:FloatArray,time:Long){ + if(isReady.get()) { + recordThread.execute { + videoEncoder?.apply { + try { + drainEncoder(false) + }catch (e:Exception){ + Log.e(TAG,e.toString()) + } + OpenGLJniLib.magicVideoDraw(matrix, time) + } + } + } + } + + private fun getVideoFileAddress(time:Long): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".mp4" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".mp4" + } + } + + private fun getAudioFileAddress(time:Long): String { + return if(Build.BRAND == "Xiaomi"){ // 小米手机 + Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+time+".aac" + }else{ // Meizu 、Oppo + Environment.getExternalStorageDirectory().path +"/DCIM/"+time+".pcm" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt index 1c95f71..40e6a5f 100644 --- a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoder.kt @@ -1,17 +1,17 @@ package com.cangwang.magic.video - import android.media.MediaCodec import android.media.MediaCodecInfo import android.media.MediaFormat -import android.media.MediaMuxer import android.util.Log import android.view.Surface -import java.io.File -import java.lang.RuntimeException import java.nio.ByteBuffer -class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { +/** + * Created by cangwang on 2019.3.6 + * 录制完整mp4短视频视轨 + */ +class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, muxer:MediaMuxerCoder?) { companion object { const val TAG = "VideoEncoderCoder" const val MINE_TYPE = "video/avc" @@ -20,11 +20,10 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { } private var mInputSurface:Surface - private var mMuxer:MediaMuxer + private var mMuxer:MediaMuxerCoder?=null private var mEncoder:MediaCodec private var mBufferInfo:MediaCodec.BufferInfo = MediaCodec.BufferInfo() - private var mTrackIndex = -1 - private var mMuxerStarted = false + var encoderOutputBuffers:Array?=null init { @@ -37,9 +36,10 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { mEncoder = MediaCodec.createEncoderByType(MINE_TYPE) mEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) mInputSurface = mEncoder.createInputSurface() - mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) - mTrackIndex = -1 - mMuxerStarted = false + + mMuxer = muxer +// val track = mMuxer?.setVideoTrack(format) +// Log.d(TAG, "videotrack: $track") } fun start(){ @@ -58,22 +58,24 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { Log.d(TAG,"release encoder objects") mEncoder.stop() mEncoder.release() - - mMuxer.stop() - mMuxer.release() } + /** + * 视频录制输入 + * endOfStream 加入采集结束符 + */ fun drainEncoder(endOfStream:Boolean){ val TIMEOUT_USEC = 10000L if (endOfStream) { Log.d(TAG, "sending EOS to encoder") + //只有视频流需要关闭输入标志 mEncoder.signalEndOfInputStream() } while (true) { val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC) - Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus)") +// Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus)") if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { // no output available yet if (!endOfStream) { @@ -85,17 +87,19 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { // not expected for an encoder encoderOutputBuffers = mEncoder.outputBuffers } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + //一定要在这个节点上获取fomart设置到混合器的视轨上 // should happen before receiving buffers, and should only happen once - if (mMuxerStarted) { - throw RuntimeException("format changed twice") + if (mMuxer?.hasVideoTrack() == true) { + Log.d(TAG,"video format changed twice") + return } val newFormat = mEncoder.outputFormat Log.d(TAG, "encoder output format changed: $newFormat") // now that we have the Magic Goodies, start the muxer - mTrackIndex = mMuxer.addTrack(newFormat) - mMuxer.start() - mMuxerStarted = true + val track = mMuxer?.setVideoTrack(newFormat) + Log.d(TAG, "videotrack: $track") + mMuxer?.start() } else if (encoderStatus < 0) { Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") // let's ignore it @@ -112,17 +116,18 @@ class VideoEncoderCoder(width: Int, height: Int, bitRate: Int, outFile: File) { } if (mBufferInfo.size != 0) { - if (!mMuxerStarted) { - throw RuntimeException("muxer hasn't started") + if (mMuxer?.isRecording() == false) { + Log.d(TAG,"muxer hasn't started") + break +// throw RuntimeException("muxer hasn't started") } // adjust the ByteBuffer values to match BufferInfo (not needed?) encodedData.position(mBufferInfo.offset) encodedData.limit(mBufferInfo.offset + mBufferInfo.size) - mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo) - Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + - mBufferInfo.presentationTimeUs) + mMuxer?.writeVideoData(encodedData, mBufferInfo) +// Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + mBufferInfo.presentationTimeUs) } mEncoder.releaseOutputBuffer(encoderStatus, false) diff --git a/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoderMP4.kt b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoderMP4.kt new file mode 100644 index 0000000..efda550 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/video/VideoEncoderCoderMP4.kt @@ -0,0 +1,145 @@ +package com.cangwang.magic.video + +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaFormat +import android.media.MediaMuxer +import android.util.Log +import android.view.Surface +import java.io.File +import java.lang.RuntimeException +import java.nio.ByteBuffer + +/** + * Created by cangwang on 2019.3.6 + * 单独MP4视频录制 + */ +class VideoEncoderCoderMP4(width: Int, height: Int, bitRate: Int, outFile: File) { + companion object { + const val TAG = "VideoEncoderCoder" + const val MINE_TYPE = "video/avc" + const val FRAME_RATE = 30 + const val IFRAME_INTERVAL = 5 + } + + private var mInputSurface:Surface + private var mMuxer:MediaMuxer + private var mEncoder:MediaCodec + private var mBufferInfo:MediaCodec.BufferInfo = MediaCodec.BufferInfo() + private var mTrackIndex = -1 + private var mMuxerStarted = false + var encoderOutputBuffers:Array?=null + + init { + val format = MediaFormat.createVideoFormat(MINE_TYPE,width,height) + format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) + format.setInteger(MediaFormat.KEY_BIT_RATE,bitRate) + format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE) + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL) + Log.d(TAG, "format: $format") + mEncoder = MediaCodec.createEncoderByType(MINE_TYPE) + mEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE) + mInputSurface = mEncoder.createInputSurface() + mMuxer = MediaMuxer(outFile.toString(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) + mTrackIndex = -1 + mMuxerStarted = false + } + + fun start(){ + mEncoder.start() + } + + fun stop(){ + mEncoder.stop() + } + + fun getInputSurface():Surface{ + return mInputSurface + } + + fun release(){ + Log.d(TAG,"release encoder objects") + mEncoder.stop() + mEncoder.release() + + mMuxer.stop() + mMuxer.release() + } + + fun drainEncoder(endOfStream:Boolean){ + val TIMEOUT_USEC = 10000L + + if (endOfStream) { + Log.d(TAG, "sending EOS to encoder") + mEncoder.signalEndOfInputStream() + } + + while (true) { + val encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC) + Log.d(TAG, "drainEncoder($endOfStream),endcoderStatus($encoderStatus)") + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + // no output available yet + if (!endOfStream) { + break // out of while + } else { + Log.d(TAG, "no output available, spinning to await EOS") + } + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + // not expected for an encoder + encoderOutputBuffers = mEncoder.outputBuffers + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + // should happen before receiving buffers, and should only happen once + if (mMuxerStarted) { + throw RuntimeException("format changed twice") + } + val newFormat = mEncoder.outputFormat + Log.d(TAG, "encoder output format changed: $newFormat") + + // now that we have the Magic Goodies, start the muxer + mTrackIndex = mMuxer.addTrack(newFormat) + mMuxer.start() + mMuxerStarted = true + } else if (encoderStatus < 0) { + Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") + // let's ignore it + } else { + encoderOutputBuffers = mEncoder.outputBuffers + if (encoderOutputBuffers != null) { + val encodedData = encoderOutputBuffers!![encoderStatus] + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { + // The codec config data was pulled out and fed to the muxer when we got + // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. + Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") + mBufferInfo.size = 0 + } + + if (mBufferInfo.size != 0) { + if (!mMuxerStarted) { + throw RuntimeException("muxer hasn't started") + } + + // adjust the ByteBuffer values to match BufferInfo (not needed?) + encodedData.position(mBufferInfo.offset) + encodedData.limit(mBufferInfo.offset + mBufferInfo.size) + + mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo) + Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" + + mBufferInfo.presentationTimeUs) + } + + mEncoder.releaseOutputBuffer(encoderStatus, false) + + if (mBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + if (!endOfStream) { + Log.w(TAG, "reached end of stream unexpectedly") + } else { + Log.d(TAG, "end of stream reached") + } + break // out of while + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt index 9b72848..4b78910 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV2.kt @@ -13,7 +13,7 @@ import android.widget.Toast import com.cangwang.magic.BaseApplication import com.cangwang.magic.camera.CameraCompat import com.cangwang.magic.util.OpenGLJniLib -import com.cangwang.magic.video.VideoEncoderCoder +import com.cangwang.magic.video.VideoEncoderCoderMP4 import io.reactivex.Observable import io.reactivex.ObservableOnSubscribe import io.reactivex.android.schedulers.AndroidSchedulers @@ -42,7 +42,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback private var mMediaRecorder:MediaRecorder?=null private var isRecordVideo = AtomicBoolean() private var previewSurface:Surface?=null - private var videoEncoder:VideoEncoderCoder ?=null + private var videoEncoder:VideoEncoderCoderMP4 ?=null override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width @@ -91,7 +91,7 @@ class CameraFilterSurfaceCallbackV2(camera:CameraCompat?):SurfaceHolder.Callback } if (mSurface!=null && width>0 && height>0) - videoEncoder = VideoEncoderCoder(width,height,1000000, File(getVideoFileAddress())) + videoEncoder = VideoEncoderCoderMP4(width,height,1000000, File(getVideoFileAddress())) videoEncoder?.start() isRecordVideo.set(true) From a11077d87860c8cdcb2e19fd15e6b5def3cf2dbc Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 7 Mar 2019 17:42:03 +0800 Subject: [PATCH 20/54] =?UTF-8?q?[2019.3.7]1.=E4=BF=AE=E5=A4=8D=E6=BB=A4?= =?UTF-8?q?=E9=95=9Chome=E9=94=AE=E6=81=A2=E5=A4=8D=E9=97=AE=E9=A2=98=202.?= =?UTF-8?q?=E4=BF=AE=E5=A4=8DsurfaceCallback=E6=B3=84=E9=9C=B2=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cangwang/magic/CameraActivity.kt | 12 ++++++++---- .../cangwang/magic/CameraFilterV2Activity.kt | 15 ++++++++------- .../com/cangwang/magic/ImageEditActivity.kt | 11 ++++++++++- .../magic/view/CameraFilterSurfaceCallbackV3.kt | 17 +++++------------ .../magic/view/ImageFilterSurfaceCallback.kt | 10 ++++++++-- 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/cangwang/magic/CameraActivity.kt b/app/src/main/java/com/cangwang/magic/CameraActivity.kt index 087d047..fab4054 100644 --- a/app/src/main/java/com/cangwang/magic/CameraActivity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraActivity.kt @@ -30,6 +30,7 @@ class CameraActivity:AppCompatActivity(){ var mAspectRatio = ASPECT_RATIO_ARRAY[0] var mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK + var surfaceCallback:CameraSurfaceCallback?=null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -44,8 +45,7 @@ class CameraActivity:AppCompatActivity(){ } } - - fun initView(){ + private fun initView(){ btn_camera_filter.visibility = View.GONE btn_camera_shutter.visibility = View.GONE @@ -67,13 +67,17 @@ class CameraActivity:AppCompatActivity(){ override fun onResume() { super.onResume() mCamera = openCamera(glsurfaceview_camera.holder) - glsurfaceview_camera.holder.addCallback(CameraSurfaceCallback(mCamera)) + surfaceCallback = CameraSurfaceCallback(mCamera) + glsurfaceview_camera.holder.addCallback(surfaceCallback) } override fun onPause() { super.onPause() mCamera?.stopPreview() mCamera?.release() + glsurfaceview_camera.holder.removeCallback(surfaceCallback) + surfaceCallback?.releaseOpenGL() + surfaceCallback=null } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -84,7 +88,7 @@ class CameraActivity:AppCompatActivity(){ } } - fun openCamera(holder: SurfaceHolder?):Camera?{ + private fun openCamera(holder: SurfaceHolder?):Camera?{ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return null } diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index ed18ca6..b178810 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -13,7 +13,6 @@ import android.support.v7.widget.LinearLayoutManager import android.view.View import android.view.Window import android.view.WindowManager -import android.widget.FrameLayout import android.widget.RelativeLayout import com.cangwang.magic.adapter.FilterAdapter import com.cangwang.magic.camera.CameraCompat @@ -27,6 +26,7 @@ import kotlinx.android.synthetic.main.filter_layout.* import java.util.concurrent.TimeUnit /** + * 滤镜录制 * Created by cangwang on 2018/9/12. */ class CameraFilterV2Activity:AppCompatActivity(){ @@ -43,6 +43,7 @@ class CameraFilterV2Activity:AppCompatActivity(){ private var beautyLevel:Int = 0 var mCamera: CameraCompat?=null + var filterType:Int=0 private var videoAnimator: ObjectAnimator? = null /** @@ -67,6 +68,7 @@ class CameraFilterV2Activity:AppCompatActivity(){ mAdapter = FilterAdapter(this, types) mAdapter?.filterListener= object:FilterAdapter.onFilterChangeListener{ override fun onFilterChanged(filterType: Int) { + this@CameraFilterV2Activity.filterType = filterType mSurfaceCallback?.setFilterType(filterType) } } @@ -124,7 +126,7 @@ class CameraFilterV2Activity:AppCompatActivity(){ private fun initCamera(){ mCamera = CameraCompat.newInstance(this) - mSurfaceCallback = CameraFilterSurfaceCallbackV3(mCamera) + mSurfaceCallback = CameraFilterSurfaceCallbackV3(mCamera,filterType) glsurfaceview_camera.holder.addCallback(mSurfaceCallback) mCamera?.startPreview() } @@ -134,13 +136,12 @@ class CameraFilterV2Activity:AppCompatActivity(){ releaseVideoRecord() } mCamera?.stopPreview(false) + glsurfaceview_camera.holder.removeCallback(mSurfaceCallback) + mSurfaceCallback?.releaseOpenGL() + mSurfaceCallback =null super.onPause() } - override fun onDestroy() { - super.onDestroy() - } - private fun takePhoto(){ mSurfaceCallback?.takePhoto() } @@ -194,9 +195,9 @@ class CameraFilterV2Activity:AppCompatActivity(){ } private fun startVideoRecord(){ + showVideoRecord() mSurfaceCallback?.startRecordVideo() videoAnimator?.start() - showVideoRecord() } private fun releaseVideoRecord(){ diff --git a/app/src/main/java/com/cangwang/magic/ImageEditActivity.kt b/app/src/main/java/com/cangwang/magic/ImageEditActivity.kt index ce64ae0..4a25082 100644 --- a/app/src/main/java/com/cangwang/magic/ImageEditActivity.kt +++ b/app/src/main/java/com/cangwang/magic/ImageEditActivity.kt @@ -28,6 +28,7 @@ class ImageEditActivity:AppCompatActivity(){ private var mSurfaceCallback: ImageFilterSurfaceCallback?=null private var beautyLevel:Int = 0 private val types = OpenGLJniLib.getFilterTypes() + private var filterType = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -47,6 +48,7 @@ class ImageEditActivity:AppCompatActivity(){ mAdapter = FilterAdapter(this, types) mAdapter?.filterListener= object: FilterAdapter.onFilterChangeListener{ override fun onFilterChanged(type: Int) { + filterType = type mSurfaceCallback?.setFilterType(type) } } @@ -79,10 +81,17 @@ class ImageEditActivity:AppCompatActivity(){ private fun initPreview(){ val selectPaths = intent.getSerializableExtra(PickConfig.INTENT_IMG_LIST_SELECT) as SelectModel - mSurfaceCallback = ImageFilterSurfaceCallback(selectPaths.path) + mSurfaceCallback = ImageFilterSurfaceCallback(selectPaths.path,filterType) album_surfaceview.holder.addCallback(mSurfaceCallback) } + override fun onDestroy() { + album_surfaceview.holder.removeCallback(mSurfaceCallback) + mSurfaceCallback?.releaseOpenGL() + mSurfaceCallback=null + super.onDestroy() + } + private fun showFilters() { val animator = ObjectAnimator.ofInt(layout_filter, "translationY", layout_filter.height, 0) animator.duration = 200 diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index efa9773..a622857 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean /** * Created by zjl on 2018/10/12. */ -class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callback { +class CameraFilterSurfaceCallbackV3(camera: CameraCompat?,filterType:Int) : SurfaceHolder.Callback { private val mExecutor = Executors.newSingleThreadExecutor() private val TAG = CameraFilterSurfaceCallbackV3::class.java.simpleName!! @@ -39,13 +39,14 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callb private var isTakePhoto = false private var textureId: Int = -1 - private var mMediaRecorder: MediaRecorder? = null private var isRecordVideo = AtomicBoolean() private var previewSurface: Surface? = null - private var videoEncoder: VideoEncoderCoder? = null private var recordStatus = RECORD_IDLE private var filterType: Int = 0 private var movieEncoder: TextureMovieEncoder = TextureMovieEncoder() + init { + this.filterType = filterType + } companion object { val RECORD_IDLE = 0 @@ -58,6 +59,7 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callb this.width = width this.height = height changeOpenGL(width, height) + setFilterType(filterType) } override fun surfaceDestroyed(holder: SurfaceHolder?) { @@ -91,7 +93,6 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callb Log.e(TAG, e.localizedMessage) releaseOpenGL() } - OpenGLJniLib.setFilterType(filterType) } } @@ -99,14 +100,6 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?) : SurfaceHolder.Callb recordStatus = RECORD_START } - fun stopRecordVideo() { - videoEncoder?.stop() - } - - fun resumeRecordVideo() { - - } - fun isRecording(): Boolean { return isRecordVideo.get() } diff --git a/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt b/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt index afe317d..40f88a7 100644 --- a/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt +++ b/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt @@ -21,7 +21,7 @@ import java.util.concurrent.Executors /** * Created by zjl on 2018/10/12. */ -class ImageFilterSurfaceCallback(path:String):SurfaceHolder.Callback{ +class ImageFilterSurfaceCallback(path:String,filterType:Int):SurfaceHolder.Callback{ private val mExecutor = Executors.newSingleThreadExecutor() private val TAG= ImageFilterSurfaceCallback::class.java.simpleName!! @@ -31,11 +31,17 @@ class ImageFilterSurfaceCallback(path:String):SurfaceHolder.Callback{ private var height = 0 private var isTakePhoto = false private val imagePath = path + private var filterType=0 + + init { + this.filterType = filterType + } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width this.height = height changeOpenGL(width,height) + setFilterType(filterType) } override fun surfaceDestroyed(holder: SurfaceHolder?) { @@ -88,7 +94,7 @@ class ImageFilterSurfaceCallback(path:String):SurfaceHolder.Callback{ } } - private fun releaseOpenGL(){ + fun releaseOpenGL(){ mExecutor.execute { OpenGLJniLib.magicImageFilterRelease() mSurfaceTexture?.release() From ab14dc86367d9e30286a41332527a56374ebb8dd Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 7 Mar 2019 21:59:30 +0800 Subject: [PATCH 21/54] =?UTF-8?q?[2019.3.7]=E6=B7=BB=E5=8A=A0=E9=83=A8?= =?UTF-8?q?=E5=88=86=E9=80=BB=E8=BE=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cangwang/magic/CameraActivity.kt | 59 +++++++++++++- .../com/cangwang/magic/camera/CameraCompat.kt | 10 ++- .../cangwang/magic/camera/CameraCompatV21.kt | 58 ++++++++++++-- .../com/cangwang/magic/camera/CameraTouch.kt | 78 +++++++++++++++++++ .../view/CameraFilterSurfaceCallbackV3.kt | 2 - 5 files changed, 194 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/cangwang/magic/camera/CameraTouch.kt diff --git a/app/src/main/java/com/cangwang/magic/CameraActivity.kt b/app/src/main/java/com/cangwang/magic/CameraActivity.kt index fab4054..5526fb4 100644 --- a/app/src/main/java/com/cangwang/magic/CameraActivity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraActivity.kt @@ -9,11 +9,9 @@ import android.os.Bundle import android.support.v4.app.ActivityCompat import android.support.v4.content.PermissionChecker import android.support.v7.app.AppCompatActivity -import android.view.SurfaceHolder -import android.view.View -import android.view.Window -import android.view.WindowManager +import android.view.* import android.widget.RelativeLayout +import com.cangwang.magic.camera.CameraTouch import com.cangwang.magic.util.CameraHelper import com.cangwang.magic.view.CameraSurfaceCallback import kotlinx.android.synthetic.main.activity_camera.* @@ -31,6 +29,7 @@ class CameraActivity:AppCompatActivity(){ var mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK var surfaceCallback:CameraSurfaceCallback?=null + var mCameraTouch:CameraTouch?=null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -62,6 +61,54 @@ class CameraActivity:AppCompatActivity(){ params.width= screenSize.x; params.height = screenSize.x* 16/9 glsurfaceview_camera.layoutParams = params + glsurfaceview_camera.setOnTouchListener(object :View.OnTouchListener{ + var mClickOn = 0L + var mLastX = 0f + var mLastY = 0f + override fun onTouch(v: View?, event: MotionEvent?): Boolean { + when(event?.actionMasked){ + MotionEvent.ACTION_DOWN ->{ + if (event.pointerCount == 1){ + mClickOn = System.currentTimeMillis() + mLastX = event.x + mLastY = event.y + } + } + MotionEvent.ACTION_UP ->{ + if (event.pointerCount == 1 && System.currentTimeMillis()- mClickOn <500){ + moveFouces(event.x.toInt(), event.y.toInt()) + } + } + MotionEvent.ACTION_POINTER_DOWN ->{ + mCameraTouch?.onScaleStart(event){ + + } + return true + } + MotionEvent.ACTION_MOVE ->{ + if (event.pointerCount == 2){ + mCameraTouch?.onScale(event) + return true + } + else{ + val x = event.x - mLastX + val y = event.y - mLastY + if (Math.abs(x) >=10 || Math.abs(y) >=10){ + mClickOn = 0L + } + } + } + MotionEvent.ACTION_POINTER_UP ->{ + mCameraTouch?.onScaleEnd { + + } + return true + } + } + return false + } + + }) } override fun onResume() { @@ -106,4 +153,8 @@ class CameraActivity:AppCompatActivity(){ return mCamera } + fun moveFouces(x:Int,y:Int){ + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt index 98e3d4f..8e4b1c5 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt @@ -6,11 +6,9 @@ import android.content.pm.PackageManager import android.graphics.SurfaceTexture import android.hardware.Camera import android.os.Build -import android.os.Looper import android.support.annotation.IntDef import android.util.Size import android.util.SparseArray -import android.view.Surface import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @@ -157,6 +155,14 @@ abstract class CameraCompat(protected var mContext: Context) { return if (mCameraType == FRONT_CAMERA) BACK_CAMERA else FRONT_CAMERA } + open fun getMaxZoom():Float{ + return 0f + } + + open fun cameraZoom(scale:Float){ + + } + class CameraSize { var width: Int = 0 var height: Int = 0 diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt index e149e0d..489912f 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt @@ -3,12 +3,14 @@ package com.cangwang.magic.camera import android.annotation.SuppressLint import android.annotation.TargetApi import android.content.Context +import android.graphics.Rect import android.graphics.SurfaceTexture import android.hardware.camera2.* import android.os.Handler import android.os.HandlerThread import android.util.Log import android.view.Surface +import java.lang.Exception @TargetApi(21) @@ -27,6 +29,17 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { private var mSurface: Surface? = null private var mBackgroundHandler: Handler? =null private var mBackgroundThread:HandlerThread? =null + private var maxZoom = 0f + + /** + * 放大的矩阵,拍照使用 + */ + private var mZoomRect: Rect? = null + + /** + * 可缩放区域 + */ + private var mZoomSize: CameraCompat.CameraSize? = null private val mCaptureStateCallback = object : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { @@ -103,6 +116,7 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { //获取支持尺寸 val sizes = map.getOutputSizes(SurfaceTexture::class.java) outputSize = CameraUtil.getLargePreviewSize(sizes) + mZoomSize = outputSize } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } @@ -112,7 +126,7 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { private fun startRequest(session: CameraCaptureSession?) { try { - session?.setRepeatingRequest(mRequestBuilder?.build(), null, null) + session?.setRepeatingRequest(mRequestBuilder?.build(), null, mBackgroundHandler) } catch (e: Throwable) { Log.e(TAG, "", e) } @@ -142,6 +156,8 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { mRequestBuilder = mCamera?.createCaptureRequest(CameraDevice.TEMPLATE_RECORD) mSurface?.let { mRequestBuilder?.addTarget(it) + if (mZoomRect != null) + mRequestBuilder?.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect) //放大的矩阵 mRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) mRequestBuilder?.set(CaptureRequest.FLASH_MODE, if (mIsFlashLightOn) @@ -204,21 +220,53 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { }catch (e:InterruptedException){ Log.e(TAG,e.toString()) } + } + + override fun getMaxZoom():Float{ + return maxZoom + } + override fun cameraZoom(sc:Float){ + val scale = if(sc < 1.0f) 1.0f + else sc + if (scale <= maxZoom) { + mZoomSize?.apply { + val cropW = (width / (maxZoom * 2.6) * scale).toInt() + val cropH = (height / (maxZoom * 2.6) * scale).toInt() + + val zoom = Rect(cropW, cropH, + width - cropW, + height - cropH) + mRequestBuilder?.set(CaptureRequest.SCALER_CROP_REGION, zoom) + mZoomRect = zoom + updatePreview() //重复更新预览请求 + } + } } + fun updatePreview(){ + if (mCamera!=null){ + try { + mCaptureSession?.setRepeatingRequest(mRequestBuilder?.build(),null,mBackgroundHandler) + }catch (e:Exception){ + Log.e(TAG,e.toString()) + } + } + } @SuppressLint("MissingPermission") private fun initialize(@CameraType cameraType: Int) { try { startBackgroundThread() - //打开摄像头 - mManager?.openCamera(if (cameraType == FRONT_CAMERA) + val id = if (cameraType == FRONT_CAMERA) frontCameraIdV21 else - backCameraIdV21, - mStateCallback, mBackgroundHandler) + backCameraIdV21 + //打开摄像头 + mManager?.openCamera(id, mStateCallback, mBackgroundHandler) updateOutputSize() + val mCharacteristics = mManager?.getCameraCharacteristics(id) + maxZoom = mCharacteristics?.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)?:0f } catch (e: Throwable) { Log.e(TAG, "", e) } diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraTouch.kt b/app/src/main/java/com/cangwang/magic/camera/CameraTouch.kt new file mode 100644 index 0000000..7bb70b2 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/camera/CameraTouch.kt @@ -0,0 +1,78 @@ +package com.cangwang.magic.camera + +import android.util.Log +import android.view.MotionEvent + +class CameraTouch(cameraCompat:CameraCompat) { + var mOldScale = 1.0f + var mScale = 0f + var mSpan = 0f + var mOldSpan = 0f + var mFirstDistance = 0f + var cameraCompat:CameraCompat?=null + + init { + this.cameraCompat = cameraCompat + } + + fun onScale(event:MotionEvent){ + if (event.pointerCount == 2){ + + val distance = distance(event) + if (mFirstDistance == 0f){ + mFirstDistance = distance + } + var scale = 0f + when { + distance > mFirstDistance -> { + scale = (distance - mFirstDistance) / 80 + scale += mSpan + mOldSpan = scale + mScale = scale + } + distance < mFirstDistance -> { + scale = distance/mFirstDistance + mOldSpan = scale + mScale = scale.times(mOldScale) + } + else -> return + } + + + } + } + + fun onScaleStart(event: MotionEvent,callback:(maxZoom:Float)->Unit){ + mFirstDistance = 0f + callback(cameraCompat?.getMaxZoom()?:0f) + } + + fun onScaleEnd(callback: ((maxZoom: Float) -> Unit)?){ + mOldScale = when { + mScale < 1.0f -> 1.0f + mScale > cameraCompat?.getMaxZoom()?:0f -> cameraCompat?.getMaxZoom()?:0f + else -> mScale + } + mSpan = mOldSpan + callback?.invoke(mOldScale) + Log.e("scale", "scale:end") + } + + fun resetScale(){ + mOldScale = 1.0f + mSpan = 0f + mFirstDistance = 0f + } + + fun setScale(scale:Float){ + mScale = scale + mOldScale = scale + onScaleEnd(null) + } + + private fun distance(event:MotionEvent):Float{ + val dx = event.getX(1) - event.getX(0) + val dy = event.getY(1) - event.getY(0) + return Math.sqrt(dx.times(dx).toDouble() + dy.times(dy).toDouble()).toFloat() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index a622857..97ac233 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -2,7 +2,6 @@ package com.cangwang.magic.view import android.annotation.SuppressLint import android.graphics.SurfaceTexture -import android.media.MediaRecorder import android.os.Build import android.os.Environment import android.util.Log @@ -13,7 +12,6 @@ import com.cangwang.magic.BaseApplication import com.cangwang.magic.camera.CameraCompat import com.cangwang.magic.util.OpenGLJniLib import com.cangwang.magic.video.TextureMovieEncoder -import com.cangwang.magic.video.VideoEncoderCoder import io.reactivex.Observable import io.reactivex.ObservableOnSubscribe import io.reactivex.android.schedulers.AndroidSchedulers From 7103a029fe7d0a650578b15644f08ceb9d0fb193 Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 11 Mar 2019 17:16:42 +0800 Subject: [PATCH 22/54] =?UTF-8?q?[2019.3.11]=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 8 ++++---- .../java/com/cangwang/magic/CameraFilterV2Activity.kt | 7 ++++++- .../main/java/com/cangwang/magic/camera/CameraCompat.kt | 1 + .../java/com/cangwang/magic/camera/CameraCompatV21.kt | 1 + .../java/com/cangwang/magic/video/TextureMovieEncoder.kt | 7 +------ .../cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt | 9 +++++++-- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b3a165a..d8ee5ee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,22 +36,22 @@ diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index b178810..859e75f 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -10,6 +10,7 @@ import android.graphics.Point import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager +import android.util.Log import android.view.View import android.view.Window import android.view.WindowManager @@ -30,6 +31,7 @@ import java.util.concurrent.TimeUnit * Created by cangwang on 2018/9/12. */ class CameraFilterV2Activity:AppCompatActivity(){ + private val TAG = CameraFilterV2Activity::class.java.simpleName private val MODE_PIC = 1 private val MODE_VIDEO = 2 private var mode = MODE_PIC @@ -122,12 +124,14 @@ class CameraFilterV2Activity:AppCompatActivity(){ override fun onResume() { super.onResume() initCamera() + Log.d(TAG,"initCamera") } private fun initCamera(){ mCamera = CameraCompat.newInstance(this) mSurfaceCallback = CameraFilterSurfaceCallbackV3(mCamera,filterType) glsurfaceview_camera.holder.addCallback(mSurfaceCallback) + //初始化摄像头 mCamera?.startPreview() } @@ -135,10 +139,11 @@ class CameraFilterV2Activity:AppCompatActivity(){ if(mSurfaceCallback?.isRecording() == true) { releaseVideoRecord() } - mCamera?.stopPreview(false) +// mCamera?.stopPreview(true) glsurfaceview_camera.holder.removeCallback(mSurfaceCallback) mSurfaceCallback?.releaseOpenGL() mSurfaceCallback =null + mCamera = null super.onPause() } diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt index 8e4b1c5..6a76c74 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt @@ -114,6 +114,7 @@ abstract class CameraCompat(protected var mContext: Context) { mCameraReady = false mStarted = false if (releaseSurface) { + mSurfaceTexture?.release() mSurfaceTexture = null } onStopPreview() diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt index 489912f..e487ccc 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt @@ -190,6 +190,7 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { } override fun onStopPreview() { + mSurface?.release() abortSession() mCamera?.close() mCamera = null diff --git a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt index 6309050..308822b 100644 --- a/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt +++ b/app/src/main/java/com/cangwang/magic/video/TextureMovieEncoder.kt @@ -24,14 +24,10 @@ class TextureMovieEncoder{ private var videoEncoder:VideoEncoderCoder?=null private var videoThread = Executors.newSingleThreadExecutor() private var audioThread = Executors.newSingleThreadExecutor() - private var isReady = AtomicBoolean() + public var isReady = AtomicBoolean(false) private var audioEncoder:AudioRecordCoder?=null private var muxerEncoder:MediaMuxerCoder?=null - init { - isReady.set(false) - } - /** * 开始采集 */ @@ -68,7 +64,6 @@ class TextureMovieEncoder{ OpenGLJniLib.setVideoFilterType(filterType) } start() - } } it.onNext(true) diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt index 97ac233..d13a4e3 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallbackV3.kt @@ -54,6 +54,7 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?,filterType:Int) : Surf } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { + Log.d(TAG,"surfaceChanged") this.width = width this.height = height changeOpenGL(width, height) @@ -61,11 +62,12 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?,filterType:Int) : Surf } override fun surfaceDestroyed(holder: SurfaceHolder?) { - mCamera?.stopPreview(true) + Log.d(TAG,"surfaceDestroyed") releaseOpenGL() } override fun surfaceCreated(holder: SurfaceHolder?) { + Log.d(TAG,"surfaceCreated") holder?.let { previewSurface = it.surface initOpenGL(it.surface) @@ -145,12 +147,14 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?,filterType:Int) : Surf fun releaseOpenGL() { mExecutor.execute { + mCamera?.stopPreview(true) OpenGLJniLib.magicFilterRelease() mSurfaceTexture?.release() mSurfaceTexture = null mCamera = null + if(movieEncoder.isReady.get()) + movieEncoder.stopRecord() } - movieEncoder.stopRecord() } fun setFilterType(type: Int) { @@ -161,6 +165,7 @@ class CameraFilterSurfaceCallbackV3(camera: CameraCompat?,filterType:Int) : Surf } fun doStartPreview() { + //开始预览 mCamera?.startPreview(object : CameraCompat.CameraStateCallBack { override fun onConfigured() { From 27b0c26b96ee8459dee99245fe6b084cf0bc8a3d Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Mon, 11 Mar 2019 20:45:21 +0800 Subject: [PATCH 23/54] =?UTF-8?q?[2019.3.11]=E6=8F=90=E4=BA=A4=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cangwang/magic/CameraActivity.kt | 61 +++++- .../cangwang/magic/adapter/FilterAdapter.kt | 1 - .../com/cangwang/magic/camera/CameraCompat.kt | 4 + .../cangwang/magic/camera/CameraCompatV21.kt | 13 ++ .../magic/helper/FilterTypeHelper.java | 184 ++++++++++-------- app/src/main/res/drawable-xhdpi/glitch.png | Bin 0 -> 7420 bytes app/src/main/res/drawable-xhdpi/ic_fouces.png | Bin 0 -> 1084 bytes app/src/main/res/drawable-xhdpi/scale.png | Bin 0 -> 6075 bytes app/src/main/res/drawable-xhdpi/shade.png | Bin 0 -> 8784 bytes .../main/res/drawable-xhdpi/shinewhite.png | Bin 0 -> 7182 bytes app/src/main/res/drawable-xhdpi/soulout.png | Bin 0 -> 7404 bytes app/src/main/res/drawable-xhdpi/verigo.png | Bin 0 -> 7891 bytes app/src/main/res/layout/activity_camera.xml | 9 + .../werb/pickphotoview/util/PickUtils.java | 5 + 14 files changed, 188 insertions(+), 89 deletions(-) create mode 100644 app/src/main/res/drawable-xhdpi/glitch.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_fouces.png create mode 100644 app/src/main/res/drawable-xhdpi/scale.png create mode 100644 app/src/main/res/drawable-xhdpi/shade.png create mode 100644 app/src/main/res/drawable-xhdpi/shinewhite.png create mode 100644 app/src/main/res/drawable-xhdpi/soulout.png create mode 100644 app/src/main/res/drawable-xhdpi/verigo.png diff --git a/app/src/main/java/com/cangwang/magic/CameraActivity.kt b/app/src/main/java/com/cangwang/magic/CameraActivity.kt index 5526fb4..7114774 100644 --- a/app/src/main/java/com/cangwang/magic/CameraActivity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraActivity.kt @@ -10,10 +10,14 @@ import android.support.v4.app.ActivityCompat import android.support.v4.content.PermissionChecker import android.support.v7.app.AppCompatActivity import android.view.* +import android.view.animation.Animation +import android.view.animation.Transformation +import android.widget.FrameLayout import android.widget.RelativeLayout import com.cangwang.magic.camera.CameraTouch import com.cangwang.magic.util.CameraHelper import com.cangwang.magic.view.CameraSurfaceCallback +import com.werb.pickphotoview.util.PickUtils import kotlinx.android.synthetic.main.activity_camera.* /** @@ -31,6 +35,8 @@ class CameraActivity:AppCompatActivity(){ var surfaceCallback:CameraSurfaceCallback?=null var mCameraTouch:CameraTouch?=null + private var mFoucesAnimation: FoucesAnimation? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) @@ -76,7 +82,7 @@ class CameraActivity:AppCompatActivity(){ } MotionEvent.ACTION_UP ->{ if (event.pointerCount == 1 && System.currentTimeMillis()- mClickOn <500){ - moveFouces(event.x.toInt(), event.y.toInt()) + moveFocus(event.x.toInt(), event.y.toInt()) } } MotionEvent.ACTION_POINTER_DOWN ->{ @@ -153,8 +159,59 @@ class CameraActivity:AppCompatActivity(){ return mCamera } - fun moveFouces(x:Int,y:Int){ + fun moveFocus(x:Int,y:Int){ + video_fouces.apply { + visibility = View.VISIBLE + val lp = video_fouces.layoutParams as RelativeLayout.LayoutParams + layoutParams = lp + mFoucesAnimation?.duration = 500 + mFoucesAnimation?.repeatCount = 0 + mFoucesAnimation?.setOldMargin(x,y) + video_fouces.startAnimation(mFoucesAnimation) + } + } + + private fun removeImageFoucesRunnable() { + video_fouces.removeCallbacks(mImageFoucesRunnable) + } + private fun imageFoucesDelayedHind() { + video_fouces.postDelayed(mImageFoucesRunnable, 1000) } + private val mImageFoucesRunnable = Runnable { video_fouces.visibility = View.GONE } + + private inner class FoucesAnimation : Animation() { + + private val width = PickUtils.dp2px(this@CameraActivity, 65f) + private val W = PickUtils.dp2px(this@CameraActivity, 65f) + + private var oldMarginLeft: Int = 0 + private var oldMarginTop: Int = 0 + + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + + val layoutParams = video_fouces.layoutParams as FrameLayout.LayoutParams + var w = (width * (1 - interpolatedTime)).toInt() + if (w < W) { + w = W + } + layoutParams.width = w + layoutParams.height = w + if (w == W) { + video_fouces.layoutParams = layoutParams + return + } + layoutParams.leftMargin = oldMarginLeft - w / 2 + layoutParams.topMargin = oldMarginTop + w / 8 + video_fouces.layoutParams = layoutParams + } + + fun setOldMargin(oldMarginLeft: Int, oldMarginTop: Int) { + this.oldMarginLeft = oldMarginLeft + this.oldMarginTop = oldMarginTop + removeImageFoucesRunnable() + imageFoucesDelayedHind() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/adapter/FilterAdapter.kt b/app/src/main/java/com/cangwang/magic/adapter/FilterAdapter.kt index 0287cbf..517f626 100644 --- a/app/src/main/java/com/cangwang/magic/adapter/FilterAdapter.kt +++ b/app/src/main/java/com/cangwang/magic/adapter/FilterAdapter.kt @@ -11,7 +11,6 @@ import android.widget.TextView import com.cangwang.magic.R import com.cangwang.magic.helper.FilterTypeHelper -import com.cangwang.magic.helper.MagicFilterType /** * Created by why8222 on 2016/3/17. diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt index 6a76c74..8fc62f6 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompat.kt @@ -164,6 +164,10 @@ abstract class CameraCompat(protected var mContext: Context) { } + open fun requestFocus(x:Int,y:Int){ + + } + class CameraSize { var width: Int = 0 var height: Int = 0 diff --git a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt index e487ccc..187bac1 100644 --- a/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt +++ b/app/src/main/java/com/cangwang/magic/camera/CameraCompatV21.kt @@ -290,6 +290,19 @@ class CameraCompatV21(context: Context) : CameraCompat(context) { startRequest(mCaptureSession) } + override fun requestFocus(x: Int, y: Int) { + + } + + fun calcTapAreaForCamera2(areaSize:Int,widght:Int,x:Float,y:Float){ + + } + + fun clamp(modes:IntArray,mode:Int):Boolean{ + modes.forEach { if (it == mode) return true } + return false + } + companion object { private val TAG = "CameraCompatV21" diff --git a/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java b/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java index 10a0363..0d626b6 100644 --- a/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java +++ b/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java @@ -68,92 +68,104 @@ public static int FilterType2Color(int type){ public static int FilterType2Thumb(int type){ MagicFilterType filterType = MagicFilterType.values()[type]; switch (filterType) { - case NONE: - return R.drawable.filter_thumb_original; - case WHITECAT: - return R.drawable.filter_thumb_whitecat; - case BLACKCAT: - return R.drawable.filter_thumb_blackcat; - case ROMANCE: - return R.drawable.filter_thumb_romance; - case SAKURA: - return R.drawable.filter_thumb_sakura; - case AMARO: - return R.drawable.filter_thumb_amoro; - case BRANNAN: - return R.drawable.filter_thumb_brannan; - case BROOKLYN: - return R.drawable.filter_thumb_brooklyn; - case EARLYBIRD: - return R.drawable.filter_thumb_earlybird; - case FREUD: - return R.drawable.filter_thumb_freud; - case HEFE: - return R.drawable.filter_thumb_hefe; - case HUDSON: - return R.drawable.filter_thumb_hudson; - case INKWELL: - return R.drawable.filter_thumb_inkwell; - case KEVIN: - return R.drawable.filter_thumb_kevin; - case LOMO: - return R.drawable.filter_thumb_lomo; - case N1977: - return R.drawable.filter_thumb_1977; - case NASHVILLE: - return R.drawable.filter_thumb_nashville; - case PIXAR: - return R.drawable.filter_thumb_piaxr; - case RISE: - return R.drawable.filter_thumb_rise; - case SIERRA: - return R.drawable.filter_thumb_sierra; - case SUTRO: - return R.drawable.filter_thumb_sutro; - case TOASTER2: - return R.drawable.filter_thumb_toastero; - case VALENCIA: - return R.drawable.filter_thumb_valencia; - case WALDEN: - return R.drawable.filter_thumb_walden; - case XPROII: - return R.drawable.filter_thumb_xpro; - case ANTIQUE: - return R.drawable.filter_thumb_antique; - case SKINWHITEN: - return R.drawable.filter_thumb_beauty; - case CALM: - return R.drawable.filter_thumb_calm; - case COOL: - return R.drawable.filter_thumb_cool; - case EMERALD: - return R.drawable.filter_thumb_emerald; - case EVERGREEN: - return R.drawable.filter_thumb_evergreen; - case FAIRYTALE: - return R.drawable.filter_thumb_fairytale; - case HEALTHY: - return R.drawable.filter_thumb_healthy; - case NOSTALGIA: - return R.drawable.filter_thumb_nostalgia; - case TENDER: - return R.drawable.filter_thumb_tender; - case SWEETS: - return R.drawable.filter_thumb_sweets; - case LATTE: - return R.drawable.filter_thumb_latte; - case WARM: - return R.drawable.filter_thumb_warm; - case SUNRISE: - return R.drawable.filter_thumb_sunrise; - case SUNSET: - return R.drawable.filter_thumb_sunset; - case CRAYON: - return R.drawable.filter_thumb_crayon; - case SKETCH: - return R.drawable.filter_thumb_sketch; - default: - return R.drawable.filter_thumb_original; + case NONE: + return R.drawable.filter_thumb_original; + case SOULOUT: + return R.drawable.soulout; + case SHAKE: + return R.drawable.shade; + case SHINEWHITE: + return R.drawable.shinewhite; + case SCALE: + return R.drawable.scale; + case GLITCH: + return R.drawable.glitch; + case VERIGO: + return R.drawable.verigo; + case WHITECAT: + return R.drawable.filter_thumb_whitecat; + case BLACKCAT: + return R.drawable.filter_thumb_blackcat; + case ROMANCE: + return R.drawable.filter_thumb_romance; + case SAKURA: + return R.drawable.filter_thumb_sakura; + case AMARO: + return R.drawable.filter_thumb_amoro; + case BRANNAN: + return R.drawable.filter_thumb_brannan; + case BROOKLYN: + return R.drawable.filter_thumb_brooklyn; + case EARLYBIRD: + return R.drawable.filter_thumb_earlybird; + case FREUD: + return R.drawable.filter_thumb_freud; + case HEFE: + return R.drawable.filter_thumb_hefe; + case HUDSON: + return R.drawable.filter_thumb_hudson; + case INKWELL: + return R.drawable.filter_thumb_inkwell; + case KEVIN: + return R.drawable.filter_thumb_kevin; + case LOMO: + return R.drawable.filter_thumb_lomo; + case N1977: + return R.drawable.filter_thumb_1977; + case NASHVILLE: + return R.drawable.filter_thumb_nashville; + case PIXAR: + return R.drawable.filter_thumb_piaxr; + case RISE: + return R.drawable.filter_thumb_rise; + case SIERRA: + return R.drawable.filter_thumb_sierra; + case SUTRO: + return R.drawable.filter_thumb_sutro; + case TOASTER2: + return R.drawable.filter_thumb_toastero; + case VALENCIA: + return R.drawable.filter_thumb_valencia; + case WALDEN: + return R.drawable.filter_thumb_walden; + case XPROII: + return R.drawable.filter_thumb_xpro; + case ANTIQUE: + return R.drawable.filter_thumb_antique; + case SKINWHITEN: + return R.drawable.filter_thumb_beauty; + case CALM: + return R.drawable.filter_thumb_calm; + case COOL: + return R.drawable.filter_thumb_cool; + case EMERALD: + return R.drawable.filter_thumb_emerald; + case EVERGREEN: + return R.drawable.filter_thumb_evergreen; + case FAIRYTALE: + return R.drawable.filter_thumb_fairytale; + case HEALTHY: + return R.drawable.filter_thumb_healthy; + case NOSTALGIA: + return R.drawable.filter_thumb_nostalgia; + case TENDER: + return R.drawable.filter_thumb_tender; + case SWEETS: + return R.drawable.filter_thumb_sweets; + case LATTE: + return R.drawable.filter_thumb_latte; + case WARM: + return R.drawable.filter_thumb_warm; + case SUNRISE: + return R.drawable.filter_thumb_sunrise; + case SUNSET: + return R.drawable.filter_thumb_sunset; + case CRAYON: + return R.drawable.filter_thumb_crayon; + case SKETCH: + return R.drawable.filter_thumb_sketch; + default: + return R.drawable.filter_thumb_original; } } diff --git a/app/src/main/res/drawable-xhdpi/glitch.png b/app/src/main/res/drawable-xhdpi/glitch.png new file mode 100644 index 0000000000000000000000000000000000000000..8fbce79b0ee88bb9c66dbdab7ede9fbe2c4d5cf3 GIT binary patch literal 7420 zcmVjr8u!OXL9moex85Y{DaAv6FZJ2OR}h)D+mJUt*Yj}SJfa$ z$&Q>%Aj2TRMt8sMzI$I4)b8%>K)?D3Yp*`N@>Ao*;MJs8mEL#w(NR#I_@CqV_`jg= z)c7$YY5ZT|m7nscN$CQAspBz$k1}!vb>%+NL#_Vzn;!O)j|?grDn{%0 zN_u5DQ8+I5i- z{rFygz<*7qB3GJ*s=8iX(wUM@sy9@)bNp7oIsCU0#};^9;__^Kymt6!E&n9rsWn!o zUsGyx>fukBiIJ|VKuw)d$KIrlzCnH3qrTswp~uhV7G(__0>dSHra=;yRBQg#hi_`S zH}F)3b2=|y(MA1|mf`{&{evdAfMK+48{&URA&u;#i7{}`!L*+k#lh|G=d2qy&)LnCG~B@4YQ!eOcD2Yv~aIzUc(u9 zE$9fxEMR+s=f}_W|6u&;&}6h1KqEx4t_Kv{9(CzU+U$HqTb+BgtDZz#X#nkaD?sL<=cft0@)X z2vItvM$O@lCC&s8gHyPUeeeQyfv$SMgb`g-4``t7;(RyZ1Ym~h!Vz8SAlrKRga&4Z z^14r_O-5z&p1dkh3cv;(Gc%;lq3D;h!ba1Kq#CERB9|OsD6GK=|7IL;*KJTQ+@+!U zf_mz6%Je=dGm%TUa7-8e4Ptym^KeQP98if9E^yXDGJzC>S4A=XL&A`&4-Mxw&5Y*s zigLXPdw`gMeG{Ebuc}7$B6U}gKGH@y~Q=5n;K17?;M-s!vnCex` zm1=vamUzogXl=@6lp!4l{vK`6f6})88lJlaaLNHpb6VPWNWo_`bAP3!0#YNw4(T4a zM3j$L(d|`YH1sc}Dk9i$_Rt$zFiwOF7>8)nZOXzO>d<}a`90dy_o-v<(jeTx-F9(3 zOACL5&zI6_xg&_t;TkWv8hM}6o>s51t-)yS)E-Ff#?){2Y1@2FWBP`4wM8b&$>}+6 z@PN+hAL$}IMw&lCil3qmeaxHontT^pU%k2_x)PGGe(55%_}dn}F1~}~kEv=-z`Y7G=9eMO^arDF)e6Y?b|bP&eZ%7hY%FsV z8o4iMTm6Z~>T6u&7NU(r)^BNMpU|25j!wgOK;Z+@7B#Io$~rxjI8d6}I%Uv`KAMXw z4x!h6$2)kj%(0MBWrHlM5MeMtt+~~r+o&BeK2sSL%_7x5aKJC)89$cxYV8t|9EmaU z@5XVu!L#lLZ2;7E^zSJ&16+O%B63bA{zsbC&uMO7&{Ct`I&-;ii#Fw)i?o())Zt^L z{Rxjt2Sf%7gX|19dI1M4=@^%&U5$V|q#@m-+>B{#K9h8RTYHq?0<~WOlNe*%2WK~f z!T8(O0kOs-lk4V&T#2-5?$KWMC)D^?0EjVC9hICtr`PrW&yK5dyA%zPyVu;B{eX76k7yDeAPf39$%0DM+%)`1=julQ+c9b$?MpSnxphdXecdj} zrcVg{_4K&%nU-Y5yA4Qd$H3Oi!TeC$wWCf?gIqlTsoFrM-bcMdAi0XBK6aar7{Bi6 zw%3ECR9)J(oKBxpCv3}!3iFcA{7-b`pHg8DAv~tUZOb4s+yf(0*R%oPJAP(L`PQwqfGj!k+T|YqQJp@IIzFN<=@Z+ZsNTz+VO1w;6C;BF&yy} z-Qo=Q7E7}=)vnSgY2=Lk1yI}8kB~Z_fn4;d@-tfc7eMqU5Ew6EE)*6F%_96pmf)+% z*7!6G%_<^xi?j;N%K;1O2HhC7$=t%@S|LF#|HfytUP)-UQF;}U(_-6|SGmpDLJdTf z_a5Mi^u{&0#ZH^Q!zsWji`J~S!3pjlh`*zSvMjvO&MGb=+{Gjntaf>%ZQ z5tg3_aZIG;W)x7;fU=JA2c()2 zyjUQOU^x1D14*e=BY+cASj!NC+ltXcIHQMGB`vRU%gE!oc41ImOA@31_i2*fp}p>Z zQMvRGB~NI&d;-n@B$mlmO;yg~sgIVv3)~r`8(m7LaXiSNwq+%CDc(-emvI`G%!~?f z{mP8U+YIO0q;bAaL%&Ij=4TvMOIbTLm{9bf~qxG4L;+CnSrAQF(p_8txOD>`ZZMn~n3IOT$JZX7(P z)2ui9!Z)-Ad+TlpXLm^LfOD~9`C0-DJ^`!L7(3^bO8=4yr2EKxE}7BSH>8}GXwMCt zCutA1fav9E`hr^bNEeN0(0j0_Q>jaBd;*H(;e_asm(qGAeT;)c-aO^j&;v3MZd~-u5ToD%5X=3k>NC^v6HQ0J6vr z=&1f1JcssR=ZL%)FRO%;N=B46bu=fVW~Pyx-h$8pz$Q2VqZrxYl)5Q}lWWU2TH|K= zaQF!2K7?sw>Y)q_%7!Uv<>nGu?)@m-C~Iw3gv{l{H&IuKh*_fyQK<3MI15{F@E-29 zFF|p@iHq=_YJD0bAKQuH#K6IQX<6(U_L^tv%3$J~71(HLJvy>D{=1Gh5K!lfszxLc zgM1_dOO2AsSm+>{1tMAcW168Cvq~HMrkb(4wCwLA|)XXGWZyowt@|EgepX_ z21ufv8J~$e8)S?Njf}UPL_9_@;%W?O-Jn)cIy|gUwwB?D61saS&7ySXdl7iX@sPj) z8U(f&1S4bcLuPBAMZja--a({#*@(izOU=yYr}+Ouda>wn9y|MxT1h+k;cAX;#-J@_ zKysVy;*b%ZXoZejR3#Bwx;IqR$28VAL7o0go9Y|n*%$cqra-jU7m%RO>3#Vf&Cp$x zG1M~?ln!(S__|hBQhZs@;Gp-ow^Q8Cg#;&%X`6mrS7~LM2U2Qy(o8c)=vb zA&ni%Qne5t^yR(Dt6KFv9~c-**wo1Q+*T+<#t-=?J#vT>aQzL^hd?G2yh<06vT=dI zM}ygLwM;3NMdLlQJ02=_BvYCOc+s4qW?^)givuH=IORpXvb@dW&DQl1B02@D1ER11 zK|AHDDWR|3u)t+eTEz=GMt?8pB~kYb?dwDqG#Zplm$MHz-~oEIL(2-MaO61*DJf~@ zHoIGh7vrw1u2HKb-P#`2r&6R8XBz zHVnR=GcNdbt1sDpDN^ zzRbESX0K03J`_Ij%#|#Vq{(E`bGntZi6f_lLnb|;HP*MIt@R5;O3BMe+!__q+M-n& zdzlF}Pfh@O+C&}hof1;h%h6}m4M&}@2VZpT&;O&=YeZpz@QSMFOiVeY$Uw{r+9Fe#%rIPRL6vJN>*0_1ccEWpq~IV zXmwU~9)~!z8s_tyZ4T$OW=vayN8s}RK?QJdQ9&`fC%6QTVUV@8EH(tRql{N-Xe%N* z%|%XoHk2hmY$R#b&_aGtOj#Ty7ZGGtiA zTt1(qBv>5xQLZIQt13)qyZHo2J||NZl3*H+byD*ljxp5fpmmjCTEy#mJ&27F>V5+0 z+{KaaK&wxp*i=RmlgBHp6P)h>EV(>+;d3dXDV9PquthpWt7u2ZWbthtsz`}OJmUbG z{$TT4Cq%D<-W%7`*bxE2(ozbUh}A-H!&gP1jaXs)mjN^E)2MD)R!teQX)Z^m}Y%no2I zgB8XDHnQCjjYijd%Ltt+xZtXa(9u93pxpZp}Bg7^TPwqLVI)MRYYfniz+57 zfyF>AA|yt9y}ppr77=nPN?4(#l|-FOfhRk>vWi$EdOWfMISEsmm2c>x_zT@LtRx-@ zxyr*W+O-|(^!ijofz@b(9-zn^fu~8D??c7yEV*4Hrv<>(PChH925kJly1=aJXoJbaWW<_@LV@urM?5-eLWRbxI0HJ?E?`2Y3(~kKa-My0SwD7WHSIya64)jEbWM*Qe5kyQ3F$Gft0Jw@VIyc|BqyI0Xrtn z@=Cm(!MG{Xjayomw(Mu9_XpGqyD-X9#ekblabpKGtqy1jauf9$Z+C%_dYCpR1Q}C1 zWLOO;>s_40#(BPr6R*`a8aRZv%Y;q9>P;E5RrZ7y>M+g%gO7cl5uPU}bBCUrp8>Rn z0Hg^GnwvED&zbHdP`O%G?aB=Q-x>>mgVW3&NSUR&DiJl@AR^3!g>}3}R`ty`-O9hA zPW1rTxg&mLhF549PJgGf>Iqd4I~}ym4x*KzJz6sm=T;R;2Y^+eBx-Q|CM;s-NnJ># zROhFcfh^Aywqg8^;Mfx9UD6@07niPV6bD{f7JGA6zouSq6F0`g#hZ|oTS(HEQeW4X zpJHWQdW1=eM8-8DKpNuJ)Ds~*8Y$Pwx{$P6)U$ig@)MK>;0kw-U76qvAfYXf=1=^ zByCbr;`Tmys|mcS@sjrJJ@o0@G|q0&1@!dXb&$He^c)u$+I0hgm?R)PFo9c7-9W;6 z;3|-v(YldIDS%5cP37`1=qwrPkB1Cz@qCUOI)Ji!E*iaqy3IkFI$0hwlFtD{In^t_ zKuOF5msDES5%-Ji_EE8!E;S`@ecS+>+@}$PT?r0RpCjdukYPtCy(-GvHe&3(xXa=t z749uS=N9z$gf{FQ+}KS-6G8{QlZB37&B&5kN+W6(9_&>>7eMS>$cTox(P_=Cas{{p zBHMojgEGk3XE5d&9B~BwK1YlzacWU4VZ=hBSvJ6fW+uir(sc@o*_yVla1cCJqUBgv`yLLd5HfMPiaJ)nX$GvE3WPse2LLfZG0dYE?jdGZTYE z97%8HWhpqRcqjUTXNXK^WnAW)D?KI(6eB!?9nNW36@?6Ax9C0%i+gA^J!*P$D)oDs zE??ldGnqUizd)Qg>N=(l*I)$?ox4}GVGgO!1jLPL9PR;$d0W|3T8`FnH&wgYD99!N zv`gR8O&_<#t!)u)H#d<+59fLnuIZ?yp(-s(MuNgFn_sq*cI8*c>2YMV32P*9sSV3g z`9KyYHWhDF+C|1dL)f=;+&reNgDe8=Ce@Qq%eE8~C{1F)34qp$MJ3&_t4 z3;@3wy@By8E(+QC=tEp)mPu~n)?7Ynbz|%B7$i%gBCP28BdqVtlFs5j`21SP1#5W8 z+3R+4155!a>-amgp}(XtjPD?Lvv5cY2V7}>B0E^{_A^;*;rorPE?nLzXLJR&cN%^G zDtGa>&yiSN;o}3p4^Pe_BTP%svdkRJDoA`I?H-B`_9Cp<;3qt;e|Y_4vRI4kKnZ%C zHqro;4Yz0YkVe@<>hbPPkgd|crDgLBUF1F5<2hI&^h3ouL&hsn@yMt-5hHI3TDk)| z^St2?k=M6mcg7?_)`u4ROjgA2{6K}*#p2m zq%nO3a&}Yx&UHSo0IKLFIzLC7TvFc-R;O64(zed!h2DxIL1cgrJmloroma8(#JV*rJFo0J`d4g^oAXzXm|e``09+Ny68Cnq`)BHv z_fawEB=(4A>M2?^x(SOaWGt^DmqnZbruBK`@RBOriyiD|XK0ur-fzM_f!1L}PTK_FRBy>QRy zUGu+4`R5e6abY9yQk$qgbvgXN)5d7WOJ06?CtfyoACMU}rK$c7rEo%9sI74Z0u;c} z{f-#F0We(n+o-P>K!5{ypKGk9xJ|w7`*C`kHm_4$RgJ5yaU09p)^pQd+sRak10`GC z5bdD?c~Ot3-+Ms0+D8d)$d?ip?ggC#+|KEHTIioaZcgIP%hgac@`}g_qqA)r@v8wz zv_v)_c+vVk=S`tHNEj$>t(M^p<3o`)P360D>aP=?o{0fN-$_>3-=I?auzN01s z3$?uQsEpaeqj|PlugkIsm%m=1tD|F`FiENGJ6y=i!h+5oLG0X@85!4~;T;rejEuMk z;(IC@o|l*!HIvoS$onhVSRh+eR}8SdSi5e}!R<|OUVe^XIIsqNdxVbDrENU7BwK3p zPWI%Vls|#Q0M)bi2|};kpa`1c?4U9aqmmcxDhWO3tfSx?cS%wfKZ@SA~?DN;WN2 z4ipj6bezh_Au>K(x0mM7`vY?f9`Mzu&|p~rV9*ZcAX1?@(%--kg} zQv|^^CH~QCDw+BA#E`Jz1&3`dO!nvn|esuoHrDKbwvlXwXHueJ>DPx-;p(5 zvSc2PVE!Qr=70{}V-S`TIAKOvuZ~lr3W4xif}!FP)Q6k5Q)~0rjQ@veuT$NGbkEWU zR%9V02QfO=GPKCx_Szo+dEd~f@4*3E&;}FPz`#RAS+VPPuZ+Yz@2;xd$^*>Eco#ET2Gik|{p@CTnCcv>1DKe-HjS z`HSQ4ms|EsOl&u_pIB0?^ps!KHsWRJTgitG&Q*pN*)lFSF(=ziD`DZ#Z3!&qZwR>j zw=Ip?$vN@L9WI3{CHoiNY`730VY;1(!}M-`hM9tih}+q<3{Jk^rDySS@U{jPYm=dc zQQ%tb^$#J=RguN_&&<2gc)4c9n!{7q?XS6R@X_X0?mXtx@1-i<$TF~0vrJ=SU{YXU z;)$_5pwDFTp__sEkJ5nyUCeu=9Gb$Jc|K${Fvjs3Y&gewj-%k9BqN)J@&Sf)7KsNv z49xW<4TqHq3L;t<`MM1bBnq-w=p15bca`|DoB58ELQ6C=PeFDAV;--;fwhcmJ4_ES zY-f|;usHD5FkzB2r-H>2nF|~ojFV-&7!E2vZE$nonDHcliGxp*-Gs^MfJ(cNLxG3o z0zm;LHOnasP0ptndm97{PA+5-kO|_;U}SoZjxLpxV2G91moo;OVk`!#R>3jn? zIc*85T&S4K>~vs%o)^Qy*&3%?7@TI--(ADjlD9}ln296xU+HEkfnA-Od^i-Q{JefP z%i)FEDI>*(fM@aMs~O4D@JE^By-Kal>_5d{t9f7kblJP-R?p9wOQMB4_vvS*FTK9C zewEq9H>b_gr7pdj!kb&w`?JQkBKQ=0i{{-qYsD4Lq`gfuWotQmDtp`Af9V2tXVp?} z{^)J{Qt?c(cANa?Y|rvE;ptyof4`KQzvt|;4{4_V_|_E9oiuN+VLq@_VDNPHb6Mw< G&;$U9gR7MQ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/scale.png b/app/src/main/res/drawable-xhdpi/scale.png new file mode 100644 index 0000000000000000000000000000000000000000..7f9266ac9b9bb897485c1d97b370d9e9dfe38018 GIT binary patch literal 6075 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 Q7XSbN07*qoM6N<$g1<-z!~g&Q literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/soulout.png b/app/src/main/res/drawable-xhdpi/soulout.png new file mode 100644 index 0000000000000000000000000000000000000000..8a0a03830b41064a489b7e012819cfc33ed93623 GIT binary patch literal 7404 zcmV&F%8pj@?el&>h`Vb z_W;Z=X^+7J0t39zm2TatYMAZqn;su~G)I`lTm6%NBoz?(@OfoDN-JNua1swNa*p?W z7BH1XCvEc;C<>3tRj@oDNm5w3`22nbas^kvmd3(atM4&Cu0q655`LIgVO4r;1ms+U zti$mBapjh;{MIA>sPN18e1~2NxtYkyzeM)Pc~yX+GE9A9#qV^DRD_%5h<$|Aaix6K zL-I;rLY?ljb7xGD|-}U96 zT}40YVmUVsOF$J}S#)(-w(YH3UVV5W?d3-u-|`fT7xnkP$kCyoD~8tO0%BTn z!*Al2-NC1wZQO{~utCcYsvhGrJBb2{>|$uROWjHYL_ zV!7m7K6pXBY9a#$aS!WBAKUbrRY8-iL`5{8@w_30J~Sgdsb1i5^%4i>0z)p;+aA4z z0kahRddZI{AQl1blHnCd8Y?4w*My1XCxW&SL9Ja>*r~dBVAt_kvW2^}{0^?6U8V3` z?8MG+T#fO%I>SqUjy-outBvTgg#zEQt1Xu7czuHoU3-h3bCbAn!w;~{`&>7h45cpC z;x;y^mQ|{1onX4>j_`u_IPen+&+K6&lhi@$CDdFa#9R`)>><}TZu{l~arCOrq4IrH zrHaj@?52@!SNu5<3rr=3`f@O|go9tkJ@s*=_tLx{rqhxDDS` z^dFfap8A(`&3(Ei6+jO-j|gN4DK4(OCdbMMmRv%qxkvb#_{a%_J3c3P$!CWi^piiP z$8~VKTE%U%fg9B(HtYbK$pEVa*Cdxrbb!Y^K64UQl%*zOa#d#0M^SCUosB=1E1k@e~YnY za1xF1Q+$j+7ccO)d=IDgm?^GBR8BE13KVjaBm|(ieZG{U-j#TLql@}D#V2(`hcOi$ zkw+Zy%QMFPE2hZTc7)H1U3^Z#=?_><*3hvj%IbUk5FKHh@}7mCGez|PTW2!cgn+5t z(Adj7UyiB7YKFo`cwkoXZ>@*;H6FkdmCptlQ_>#(Yx*O8u&2n|F`{G@lj#U$K2?Pp z5*`x}Mzt;iNmjlT0q=NJwLK(0H4|~QiEUx%W~#)L*FEWUtr-}(Q^-I>`^Z)r3H(uK%0vnFb=*X(*JwsbGqFYW_eCx zGQxM`J^aBx!&Bzx8LyHvUwhugrChd;>%3t?9O(`HA_nK|e(rvky}|}hR5x`2nL=&A zcMPi;k1U*ouEkFk8KZfPQ-;KV>+Ve|d`ZAes1o5R-_kpWEgsJcCH&qMrDldX2nIT9 z8bHtwGrF&6rb~iz&#dF0(_8pF-bSa4@y49tuLQe4x;^|H9pi-YQADALQFD2-Q1KjG zubA8{J_-(aB_&=Wr+z6%8(pb{lT?ROGNsZKnEDe&Nr8=X;8(3%SoIxz=C@I{N{ovP z-)FCA)tD7%!6o{I2a1<9Ym~Ly8l5u6TZ`J;aV>n++Q1j-4pyobPRkL#FMr0LDEuRP zj?>6d_(ZF^NGw&AYBIMZa$|9o$-j$uhg@^H%r?FsJep4tNu!tm&HXc_^j0A&Mch3)Iwe5WLKe}vHpV08I9kl zHjZ+Hszz}el+aMv35%ttOo4I!QcLnL>9GS!@>#rvqxd?G$QAN<;cU(X(xk!aMVl;1 z1@&6g-m358u35v^(QVwq7BZ5K$L<)vFCS~nUnC-Agy4{&PB7ljO{}TTm^q?r?E5<1 zdh-s^YXz9*zu@k!22QzNQiju%s5J_fBxmY=1OX$XmL8Y;>% zkz+#IH05^^K0>)P!|7$jZ7IBqGPDX-axTU<@h-khZez`K@N;#9-}~qIZ}Wnwkt{4~ zYx(PaQ-K!&xMV@L)~+=*r&tKA$BUOZw}|yC)>lTNg^^e}_>5Y=R&Gw=tGEGGufQvx zsghYLPnaJ6R=v`7B_wSRSfJgc2R!r_IGRaOMMbICTJ2`VO}<4Pi9$nf(|K-_gAW+@ zEbGbVhxoZT)R<2r6FNCqfQha}SQo7@Pd9aE!Ymow(p-YNQpRcpw1jVsHKiL=65M=Z zl+D#WHAHpW%W@6A?g60A61s9LSdi=cQ^xup*L)*8!yRUXmAH$$Op(u7O-{JRqG6{} zdCGzUZ47-kA(Xpx{~cChp6TOFHNswb%!+G*UblxLFYBTpn5+TMMo!5d;;rJoO*%u} zAxh{y>rRRtqx>R5Q-ub;R%k2(=ag#P2neY>P2KTf`l7Ry)lza<%I_Jg#fa&$ARZjD z5PMl2V(53U-5-!OuHl7Qp**Y}8SX{Rcw=!#1s%Fx$QtkL>?`H@hhsuoJvj94eBsdP%gTfEP$?k2h}()6?3wrVKqjLvb$ z`yLfjj3r*tFjJ#fmUCK7qqF7}Qd>CG1S%0sh@|dhQFh8&;t03g7HgOm?)VM-=#Q|^ zvb?G<5PR+6#YmYRBIeB7$#pJr3xlG?15fd&e8r2Lp_Et~PcALkpsupG!dT-rQmYoy z<10f}kZ-}k71zK!`5DjsRdJC?{*kX&$A3$w%^LIF|LeJ4xBj>t}*6ANDC zXShcoT#Ht)84b`c6I}RAaf@a*sy0EyU6z-hbato=x_^1WcsbK1#*}4kr7mCJYT5Pc zx1|V0mrc3g9vNeYAh6DCv1ZzO3pqKXkY|3#JRK8eQ&oyps}(xrDNbWH5Gn1i&rdLo16gW5ICZCOGFMgj`hW(O%nC z?;RS*KuCPG5O?kfbtr@yi1ApeOKBA*!;gab zcw(G7T8Ak7(A>sv(mUjq9cUA~qQg_!F>kV&ZfN24%ycC#@n^qBPne)(20A)R^GdH9 zYus1r1+O{p1=3(}doV z4FY1)OR+b3gTLe~sz|R%OK4eEZskqM#=GRWiKdFuM<=B1M?fH{IB` zFa7~9P-|E{-`4-cyCen5J)}mLB6La`I`m`h?L`zYFq%XsNGB4hW};@38byqu*K63~ zQt#rU>seC5@*1r^t+Gl#hdQA8Ba zpzU;NlN)7zR133C#f-oTB!Cn1HG_gFkLn{9*`)zz5dRmiYToj145PC`vmg#ycng?F zmXq0^Fg{BGnsx`?>kU_g*IwPyd|ogT06=-@sw#$2IQ(F6WHH;rtlFR1TS`Tgi~*-2QS+44_zAa0MV`#58|#y zwzzoh`Af}imvmm|f%j_w4L1$sR4y9p?hYwR3LMxBYwb1MCMTa>kb;ot^xY{v!)f}x zPj>K+(OssfHZzdHk)5%Qo9O<4wnr^WHOC}r5=%|t-x~#9mF8;3m}rio>y!0nG)7s5 z{zV)MYFn>)bx8Uv#K%$rZ5d-J?aU-ODnEa>(gO|CRH@w_p5+QnGHr72gw}suoZ`>3 zr&u4XVv`~M8HK$b4{$;Y3&P9(f`M;ihc$@I5>9&tFQ;RifMPodxj`98RbNoZ^8rGSDgpaS8RmOg$Pl@}IjPwo5)&D&? z!q4td@7oq8;k{;*kqk!ESARJMh+!CPs(_T~dFXPb#uMR^l38R>;7X@#=~z@?3ysYc zk`~$8lGrXLHebxl-z~Y^O4E!i^px%{orwW0+xG!{sCxXtgKU<60r6k2mz}s zkF1YjwLw2zu^nZr6K0lUf1%!9YBeYzAv>}7e8XB@1`J!w&pldi!w*OsTR17k1RWc8 zXT4>8DBF;Dls;P#37!aaO%#5l)b3K4ZUM~-XH?Jy%j_4-`Nu+>5SkbnL1{=M2ylu4 zI?~%R(gR>+D&A=_B}-u@aCzqNZtD|+q!B|<-HBM{G32Bdds1HDtePkgI@T(&u^~fB z$-3{x*vpRa8LP4$U4PpRXr(TW{F&DA`PIorJp&595j;R!*F^oi>EiOa&9^I5YKs`u8h9_8N7mfK7tvwpk(V zP3S!(4rgaNWR=l*6*CQ**lIK5qAIjs{%m@LadL<5->0kgao=z9gb5CWy$K$Qs+i-QUoX+a>Ag877X;ni0O`<}9we3Qyt3@GOHsofxYAKT) zVT_P4vUGR4K54VyMroRKwPbPAOo!fHi#%lJx5i+4V6HReuAz)0?46(Db$O0)o4}vc z$3^wt9L-viBd0U$R~NX?Y|yXTxK8xEM(aKH^;|A;S8f_OS99ZLj^|9s!p$IJL=PK9 zITaw)9}6$Z@Fr1Qkn?lT^vN7AyNISCX<4JX*I8!d26DNh!X^@0frR~%hk9puNl9w+cYA+p-rETLeo#(I|(!wMY zbWKty<<%ZSW8%+bRtK7y9=C zLW~5>2G6LczvYM6N_rH%jT`AIJz@(l$NP++hyYTA$!s&W&{%GEe=YC(4!wV0SD3N` z)YbF(%TZJ-&epremY8*OFvlJ35Z8b!Q?6`A?CRhlSAK&;OI9`{kNMx(0bUV! z$Mp`ldXF`|+JJ4_u0|vd{P$I}wob_^-26Mg^jfRLhoOws`GQk=tlb?Kp+n+x-(ANS<~Gy$ zI%WjB=OjLVEB0{`lSjzLDI0=CcDG7DvQ~FcxhO8*hbEdjN2cnf~UMIuNKBJE{5q|-TyZm^Yi0NcVrpEPlR(anh*SH&P;~(OCT<K_y4o9p}k`364(h)XzR%9ro?rE$(E2JX?jtE z<06cfDm^XI>QTKYir-Rz`GwLpzS0a`-qxYNbZltmIxUsu*lrQyW;MXSbRXiINJ8 zg}?6Hp|<7M9|n7F$l|Hg-n+~gKSa$MMTo8|U&rr!cK-LdghHYZ6h0G)oWc zvzWR~ruSR_DgN2rCudnBOi%E=dx<}|C-}*}BA%l^#9yOVm~{xu zWxiO0fV8RiFNGT+>h(eh%$uU}>ceIP1&4a?tmN8pISmC&N+w5Mze4-V^mmQ5eV6zk zp%S*qR6)zlZZ4Ut@jKZ@7q&Oj6-{_uJVV!5{*}6CDa2`r#z-r>n50d56}Op{zM8rlm5Mjb6Z2On2;%Y>38<-z3)@SD(@Ts` zO{rA*hw1=XE&!6@0m;#N)W2>19^7T;Ta)>Yf zYNk=*ZE~XJ{b@*SG99lG<*%nJ_$0ZGPyG&Vlr#Z7QJSYun6m#=J*V(*v>_wuTpBdy z$`;`Hz3P|vod8^)VQYfqWXFe$z!TZOlao}uK|vyP&eZa0dX43#Y+GML-*SPZpZDzr z-mrw;uTEJ-3Vq5jq9SC>UX?+i?o<+;_#!cjSJ5t0 z$OanuO~Q{PZQqwq@tRcaEDq^TyhwkFgAar4m7mSk2l~rgRYy^(h|oI}l+DZ;j_KO3 zvoRjVTe!!%VJ#cr4*9}8vd1xN?h`-6K4Wsv^NAB$YK$qv$bF!@luGY_;kC)Ix)!fc zq3b%fU*&T%qLK-|Wo~dlF7lk$e?nz{=lAj09um-?p`-2nrOEY&9%g%ccj=H?YIpf8 zTW@12q<=_7q)CTdp$|i@(4UsL!$o~=@8V(mzFKRIDN=s1>GjuN1&+%xEj3|jJYsb? z)|B)<9}w$r)6F-cE>>&{1Jel|>fGsIWx|5$Fdu7W_lRuuDgEmyl`u=>*K3SOE~8{qe0TYer=at0}`~gafHgtCtjfsKTF%h3W4``jlpo!E#k>6$U3R e6iXkY;{OMco%47EN<9Jq0000%M`cIA1wRn`uG*a+^FC{ZoBt!c~Mw%Zeqi3x{ezRd^6e4d}8A2pvQ>}Y$&VNdsr zTWYB(QQ}Sz1OX6hty#G|pL^fS0zgu1YFvyYYRSrc_uYHXJ$EUyvU17dM?NYT6hVkG z2moUdVuq;75ka0CRPq_0p$btIM!vvz`B(op`W-!>Z}D8ChrU1nV+_JD;4?X=1Z=@0 z=VeOhxLz>X6dSxMWNks!8|C=%8nph zrjs6mppBr>Mzpw!sMSHYw}5u5iEgKjW}5m< zoaC_04i>JwkL9bkkgQyV?XAPM7HP!^S`BVaQ6i`2s?uvq{5No#DrnLrg%%_TW7!-6 z7By+|F+ppFe7J|=t8BrW7z-E4e<6dB6n9UMNrkAnwyk-d0K>lt$s#}s}- zRTBm);zt#$8=gMSig&spKWOUq)e|YS<>`~mVitCgZd}FEwV$GW=|eOx-awUfkrfrD z)P|*&?8ysMM_U*@yQiR)=TrJ?p+ej0?6obXv|rJ7(C8v)5+B-&h?Xy-y?hDD!V3L2 z(f7l&hsAUm>EbmEdEMQozlRwPBDz6 z^d&KGMsDN;+KOhJUj7M|Z+wg;3T%=N!Hk=dA7Qk;iP6qe6#I`+9d5!-4p9tFC@4+R zmJSp(OgV2fmz!xtef@0wk zF05X`@uM$L?EDTven>N~5SM}ShpEUgU%Nzz%7;+L72YVgET+<^A*u;!QVqq z{QjSz^TD5@yRxCz$i_pApFcys^9YAq50LFYfjN1OXu1#C@Pr#9p~!~<3eJU?;4~st zE=1}*NrWu@UBJzZ_fQ@@#$c-n5JL<%xpnaa1Z%fY_OGB^x{US9R}u6VG5RJ(v3(ci z$)18_NDGncl)?h6BSFSgKKvO~XxMJ|K7U1O-4k1t?m5B6hAHCy29|&JpV0sC7l~CVa`w%bhe}TiVevR47CkST;XxIsw1e_=gl;v5Ku5=4fic?Em>S18#@005x zre+eFf0}UTiZP0lT?}8oz~E>f)7e<9rqk)5)mucP)ulx&5eF({$H%-Z0XU|jD&Y^2 zk|k3M?1dH#cwKN2x-5iq>B958;03z4{5Ss{@r}=rr%Nt;5oL$p{%^ea@*j~u{vBE6 zNMo!3n}n!}nw6_mCQ|Kc1atrC##?J~9(c}MTv=6W84{;U-86=|$u8sj6WGBH+A+0B z*C-fSg3bmtzph~|A088YC$zFag$_K!v&tevwP5o`jcce0H2&JKMW5^A>VN)Qq&I(# zBE0}p=9un0#Qqn5kCWg13SmyfZPPDFHEo{J5@rfwB}11SmAZ{*Th*yWInMj$up{w4 zWpo%b63mJzBCaQGGBmar?&uEL{!<)3dk7o1(O+1{EG8f}7x|o^NJR~vJ%f<=px_25 z4YrQm8B=@Q>w+tHCNaqwK+M7oEdSMiLvr+_=bj;qDq5AN>-|TYrXRaf1kb zjN>PFsOA5KeCt~ZG-4vb9Bi(^S|E?u#{CV%+{~*&zg=+m2;je<=L@yf6o`fraTDYS zXd~~_hj!0M*r`Bpxpe5adM!h9C~E7Uc04+C`JGQGZETEwc|Jh zS3!IXe=UPLJuJ-pT;MbtG!#6jjIJ_p2|l&U`_a@(T4OOfMuY6Hy|hdg*oGm)obj8} zm)q3lw9Y9V*cmnT{^wAE8w4d@va^cbhd)Q}#wQvU?C~}ZzWHZl-~BV<{1{2-!cL{a z7)49bo;7C|Z017iBK5pTQTMbM0dFbpyY=B-2mYwv_eN@5*i+!B&(n8EfF?smqY_E8 zMWM(dY5rmX)1z&aqeE^S-BS2OPY;tQ%I$wOjc*i+faF(H7SLWsxY3);*GxV zwK%V!oGCX^C|8tX!sDN-=*GCn=)CxxVK_DRp5Hg^uZ=?#OvKmr;;>%Uuma-)uC%!M!`grKec#w$NQC)xG{x z#`!*D+=OAUa7vQ(Hv*$K$%|PH_iIwBG<#H#(lZx4G&$7@cfbro19?p z@m<*Q0ScOaTT}gD^cpE|*awJ!U`sl}nVb!5rZPlkJC?KKpqJeXl zb94x34T3>Ps+jL?W6VUqJlP?Aw7Qc{8^Qx3?wa-S`FxG5qQJItzG_Q@oIcjL_#Ren z+}4y*a{BD#5YxdiDKU99ubD7UpB(R!@zKPE_EPAX@NFg}C0xvPkcM-2GjDP=Vn}ef0jLCX}@`x-b(WFI8 zUHw|KwmI8AZ)!D2St%J4&zlR(<~~FJ`p1g4F$HgMQQ=@f9zIda=H^s5{2s>#W}P)g9VV;nwzgoR6=YFLXGE@0Zbh*3Ik zb}tJInb^w>3*LNsw98Po%iWvM+#=+n#oDsw@w&pi(p!flv4l9#0%S;T-XfYc|iJ1gNHZ3KXwHK7H>sHe21$+a0cB}1cTCaQs zIEec>8xYneAD@X-us$NR8Tm-lL6Ku}lm zTs}8dRaXzZe#Ng6J(R>qT1xGvi`)5d3H!$Xjli?AseSjIWry^>hB+Aa>6i2g? zAVafdjl_S0jq6(HYLep)$3x5xNnEli{f;-b5<^46+QbtF>pCGCyy^E=wB6A6t(hX1 z4eKl+zWhgsmTzFfjf(9UyWjpA?IgzH^-qw{rE)>?++dbj4U4YE~pv@$_Ba37H!nHziCoM0bdwy$?Rch+0e}A16rW_x~09aw3tw+h3zb znwm_QqNOMvGWU;?85c=ZC+u1IyuXO4C2=}FqA5-l6YLquUo~}!&6@v9=gvnQ5t=zI zOcL@QO+-8-EJsq{PiRs>V6^HX?4~SF=|^=YY@8M=tw&HXLfNE=wt>nicXIVI8rMGJ zqF0EfExOH1?0okXierKbGpWJzHB{?2(V%;^xZz>SD4lE3hPcos`x-N6m~bsId3R6A z&6GZS=J99f(~S2l2+m@YlG}%2q>>1AVYVd&#ZWJvGsad`{l&AH(|O^I zg-2RPDb@9oKKd7LVR7RomEY2Z&i1yD-~AGCKrPUGv%^iyUfk1!rx6*nQ?f)Ua**Hz z+=VEn@KGBAx{<`+w68#EB&q=CH2y9RsRreR14)rHiUK$&qQ*PSXrd@oU?`>;5wUpd z#cEfQBq7gd8oDYXa%=rP^sjt`*5VpBl3f4r1zz6yjmA{X=6DZ#_#DNfe?_@_4+)8j ztSF9}R0j`&To83AaF?6ZVP2nVVWCOCOr+B4wN+;ZZ%S)l6MQn|{^$B!EIUmadR`Yy(w&SJiFchR1ALC^EK1NTzp++m$CKj}eGg4L(&|tcH?@(#WPytlplj>3jlAZjG@ES& z!yt@M=X0-l`JV`iw8|1$7E4&vlo~Ij$__+ME=Y zwvF!f+elW(2WY0nl(dtn;e^5&`gK{1T_HfsT8@Hol>}+<^ecqZ9bSp)qO_10|4l@s zk5jdpn8F9#5X;XRt)7NF$t6$=Z-Q6Fbhw z!MzqUCpF<_^|AQgXNY}MVxP{_w$ z{tMPuK7kcR#eJ4Rh$44=uS8qY5z|cKNP|>!F@d)G=$dvq_0^oxafrFm2dcD&x@$#J zM$4haFmPi=nZ`nsrFSMAxFnSnZX2PiWhg^Lm1=8VjvYYLmwksD&=41&<;nFe| z7gyCp$Jva`k7mx0)V%sJ`t94KqFh>V#ut5q!4VC{{^$bPTI6!D(CB1C>FqR^!h99+ z8j&wgmm20Isj69awDZwwO z>vFE(Ay$?%%gY#M#&nlivcTIoL8P=L(+xsnbG=Wl-9P*QIzq{Kv5Bc)8q=OJaG zN{SF-#xnw;6c%+f2yrE^XJnt`4RZK8+^H@3j%(%GkK&2}a|XlsugaP--6f!6bfA_K zh2+BJl%_sp;y+_zB4L9BWZYr4_VSsF)mn1$#U`JY3J^+|D5odbd+>!85n9U^8J1F4 zwc%;85S(fV*RVUw{yp%(H3yo~f?}4I)Bh?aNwUadGS&`=^y=dA2<2#?wexEDlJ^`) zd(fAn>RP<-jmdNrGetut*x9&&CaqW4n$+IQ!45g>k+zJZm>MtaF~reuH%K4iQOd0vO9WK7f6n@jCX>vOK&3Uk)a9rd){ zOjAY@5jq=EIBtF=G?Ph*3=C-5nj146Q24PFDh7!1Avdhlgjmbg-V%*A>!gB9zh=(g zy?6s@tD_b>C5{z?JuP$ubYE&bS42PDe2Df>M#x*dAY*2Wgk-X_iR$^?dF}dCaHEK- z<>Zt;IXT3^qZ7Q;7%7e)xkJK85Wcx!QZvL*JB&Kp2F8CicjMbkJ{h%seomW+Vy4FR z&T5StZ%HykCt)_H;8?vu7I{gpQ;Y|g?!REdB*m36x2h?(EOmsU*m{Vg-5vB6ucF<* zfauaS9k4ms`i^Uqxk?k8|3(~*+Z5!AXDuVXlqfOd?@{xi%5&*=FX-73{YI;yy6H7HrY{a+)xcme4` zimK5^d+i#QKl}uHzj?${6C`1!8dY8)HF6RS8MM=3JF!~rTc5IL=OVRppSAaTSio0m zg9gJ{RD-i(h^zMa!nW3(>(5+@sUgxh{UwnyD$Ha6@WML!fBYF1S1&UgYrwvIgz>|# zkss_MHHi)>=3-f;ae$zqhP*l3zKiMJrkZj{Zk(=P2c!i<7E%^cz09;dEMp>~NlStu z<5hu#58=qxoDL_~wXrkisLap$!+Wt(Hm&w1`d2IWHUQ>Bzo(5G>xXy?p`a98XIuumC^1)3jF|4DZ`1W&ekfsYqk2HSH6}dFkgrXrVEVa3p!+H z!$?ti_F_v5j)8CWsEOKn@AI@kVNPG0Gq2zFn5P=%>^Exz@MT6dXNMb7l+#IOVUVTE zNXnc@y?Ptn5C013+WU~k@bqX4M_>O6)xi^HhU3$55KU5)Ng22jR0G-I2nWyZWBB5U zR!~C5^VQpb!tCo~?r6s~{B=|#|APX(^TXg&rRN@XC#6zQ3#wfI)-`+~P}(n}(CA%3 z@7kZDzwt-(fF7od_eYFK!|m@8mP~y^$L7j9x2$K(*g|AH>sQ( zpQ71aC2?XZOy3@T|9{D(k2PEiE=i-IE~K6t@RP!vaO7b05)&f)L3bI;OIL{aEu@z| zM(d|D)pox1HT`{xG>u)6vMkTk>3*QS^QuV3{DDoVGyh-?gwDy_nEGXzAk%pTD|bp`+!D>}kj_)?LKH(h3Tq zc#oM_qunMUnCLK_OcY7+-uQyRxrwG9@i1rpfBh4qTB^twq&{e*h{^Ol1AMYK+ytZaaidyTp+X(TW3{bUQeD{4EAd0ET37a>3C1V+bFv%&F#1A3k02m94tK z@k0;Y%0}1y0wU}prrY&y{~QVZx*)yQYRq&e>@L7SU-MX0_ zYW#fo3H<0IH2+PRy%pIQvlAu<6js{EK|=;?mXKb!hUVIJ%%rtU^REabmSH0xgNhZ2 zOKncnas2c?o`E^4qN7^c*_OU~Rcd!MCYer?P=A_{Riv#U)62D6XnyeL=zaK0G%kJw zBP2^Im4}aU_$7sY@Fj@IT0#gEeXUM7>fMd|^-|@958JK9&*u!mmzXOZbcN0{G$ed* zAu?i73=W}TlYSO9dx-lhXe_KD>2%R(HpyILEm}$)Tf&{@R-tPzOURsK**q@~oEEH0 zePqLJ=zhc{jg3Nn;1jl)Ufx7{^(Sb*_b2GR_aD$+xlS@d1!N;k31r7#{R0LMe+xU_ z7Y?r0roVtNE(5HEweyL}9~{(o^W9cTJ?ala6f)$yPq`qHnc)aM#`PZa-5_nE&pdqA zY$0D~8o2kIZ0RYogXg+Y5iLOG%w*OEUdv8!ZwG!DN1Y^ev+6RlZpo?BZXdl18|ZFa zL$$bpfT1d($%jS8w0awZXAdyg{2JAZZ16N0cdNAd6{4gua*IzA7D;>0+ zbISa8;D#a)H98cW+K`$W7qfixVJ}+69 zw5z3WfAsVLQTzAEsqF|uw4;m};Z(<23K{YCiRT}pm9Ge1s_aqa=U$DkDodT9@|{TI zz(V{lFJ)`w7X&L0T!N+VSM9FS+e9Xs>ARt4coOa^-|lsI66#Mfbbd~( zi(nR8H!GEoj(DR9+1Cj+@BJPpTg(!V_JPqz`9m5=&2GxYj-^dApQp9|KZ1*f84~r- zcYN|x)Ki9Da0@@FP)f5=rXf1*I-s5|)TlX0;4tdH$K8^9rq)eLaRJ(O4<4uM(gt`H-BGgj;;-Flj!eQOtTNuTcS&}(d+imYPIMt(*BJd z$XSoJ{7+n9%misNJRyD9N3pl113WUuA(daT2BR%~nOI_2mGZJ!4lSb$bDc#$E$sba z!Cygj-!ZLG-#E522O`apUAU=7uk{xIQtFkrqFgCrH7#^p)eaLB$BA}MjtF2w#^x}p zn|N-JOBuLzJ*`52*-+?6NWl0XF*c>CRCo&oMQOPP7V{}bd7*Q4#>@v8f_G%h=L9!z zc;UvftNH7|mk+Fth4>%2`X52-4`HqU{ih5H%ce*u6Y(<0Z;VcNv?i+lM>=P3t6874 xjsxEsDrZ_DnwK<;`z@(kpY;kpqo8+q{2Nnlv;mPz&|&}p002ovPDHLkV1fuRUJ?KR literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml index ec5d13e..ddbffab 100644 --- a/app/src/main/res/layout/activity_camera.xml +++ b/app/src/main/res/layout/activity_camera.xml @@ -44,6 +44,15 @@ android:visibility="gone" /> + + Date: Tue, 12 Mar 2019 18:37:07 +0800 Subject: [PATCH 24/54] =?UTF-8?q?[2019.3.12]=E6=B7=BB=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/glitch_f.glsl | 6 ++-- app/src/main/assets/shakeeffect_f.glsl | 11 +++++-- app/src/main/assets/whiteshine_f.glsl | 1 + .../cpp/filter/douyin/MagicGlitchFilter.cpp | 3 ++ .../cpp/filter/douyin/MagicScaleFilter.cpp | 11 ++++--- .../filter/douyin/MagicShakeEffectFilter.cpp | 3 +- .../filter/douyin/MagicShineWhiteFilter.cpp | 10 +++--- .../cpp/filter/douyin/MagicSoulOutFilter.cpp | 31 +++++++++++++------ .../cpp/filter/douyin/MagicVerigoFilter.cpp | 5 +++ .../cpp/filter/gpuimage/GpuImageFilter.cpp | 8 +++++ 10 files changed, 63 insertions(+), 26 deletions(-) diff --git a/app/src/main/assets/glitch_f.glsl b/app/src/main/assets/glitch_f.glsl index f5e6661..14ff355 100644 --- a/app/src/main/assets/glitch_f.glsl +++ b/app/src/main/assets/glitch_f.glsl @@ -10,8 +10,8 @@ precision highp float; out vec4 glFragColor; float nrand(in float x,in float y){ - //fract(x) = x - floor(x); - //dot是向量点乘,sin就是正弦函数 + //fract(x) = x - floor(x); 返回x的小数部分 + //dot是向量点乘,,sin就是正弦函数 return fract(sin(dot(vec2(x,y) ,vec2(12.9898,78.233))) * 43758.5453); } @@ -23,7 +23,7 @@ precision highp float; float jitter = nrand(v ,0.0) * 2.0 - 1.0; float drift = uColorDrift; //计算向左或向右偏移 - //step是gl自带函数,意思是,如果第一个参数大于第二个参数,那么返回0,否则返回1 + //step意思是,如果第一个参数大于第二个参数,那么返回0,否则返回1 float offsetParam = step(uScanLineJitter.y,abs(jitter)); //如果offset为0就不偏移,如果为1,就偏移jtter*uScanLineJitter.x的位置 jitter = jitter * offsetParam * uScanLineJitter.x; diff --git a/app/src/main/assets/shakeeffect_f.glsl b/app/src/main/assets/shakeeffect_f.glsl index 9b15e75..20a195e 100644 --- a/app/src/main/assets/shakeeffect_f.glsl +++ b/app/src/main/assets/shakeeffect_f.glsl @@ -1,14 +1,21 @@ #version 300 es precision mediump float; + //每个点的xy坐标 in vec2 textureCoordinate; + //对应纹理 uniform sampler2D inputImageTexture; uniform float uTextureCoordOffset; out vec4 glFragColor; void main() { + //直接采样蓝色色值 vec4 blue = texture(inputImageTexture,textureCoordinate); - vec4 green = texture(inputImageTexture, vec2(textureCoordinate.x + uTextureCoordOffset, textureCoordinate.y+ uTextureCoordOffset)); - vec4 red = texture(inputImageTexture,vec2(textureCoordinate.x-uTextureCoordOffset,textureCoordinate.y-uTextureCoordOffset)); + //从效果看,绿色和红色色值特别明显,所以需要对其色值偏移。绿色和红色需要分开方向,不然重叠一起会混色。 + //坐标向左上偏移,然后再采样色值 + vec4 green = texture(inputImageTexture, vec2(textureCoordinate.x + uTextureCoordOffset, textureCoordinate.y + uTextureCoordOffset)); + //坐标向右下偏移,然后再采样色值 + vec4 red = texture(inputImageTexture,vec2(textureCoordinate.x - uTextureCoordOffset,textureCoordinate.y - uTextureCoordOffset)); + //RG两个经过偏移后分别采样,B沿用原来的色值,透明度为1,组合最终输出 glFragColor = vec4(red.r,green.g,blue.b,blue.a); } \ No newline at end of file diff --git a/app/src/main/assets/whiteshine_f.glsl b/app/src/main/assets/whiteshine_f.glsl index 9821491..583a21d 100644 --- a/app/src/main/assets/whiteshine_f.glsl +++ b/app/src/main/assets/whiteshine_f.glsl @@ -10,5 +10,6 @@ precision mediump float; void main() { vec4 color = texture(inputImageTexture,textureCoordinate); + //最大值为1,色值全部变白,最小值回回到原本的色值 glFragColor = vec4(color.r + uAdditionalColor,color.g+uAdditionalColor,color.b+uAdditionalColor,color.a); } \ No newline at end of file diff --git a/app/src/main/cpp/filter/douyin/MagicGlitchFilter.cpp b/app/src/main/cpp/filter/douyin/MagicGlitchFilter.cpp index ffc0947..ef3aaac 100644 --- a/app/src/main/cpp/filter/douyin/MagicGlitchFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicGlitchFilter.cpp @@ -54,7 +54,10 @@ void MagicGlitchFilter::onInit() { void MagicGlitchFilter::onInitialized() { GPUImageFilter::onInitialized(); + //颜色偏移量 mDriftSequence = new float[9]{0.0f, 0.03f, 0.032f, 0.035f, 0.03f, 0.032f, 0.031f, 0.029f, 0.025f}; + //偏移的x值 mJitterSequence = new float[9]{0.0f, 0.03f, 0.01f, 0.02f, 0.05f, 0.055f, 0.03f, 0.02f, 0.025f}; + //偏移的y值 mThreshHoldSequence = new float[9]{1.0f, 0.965f, 0.9f, 0.9f, 0.9f, 0.6f, 0.8f, 0.5f, 0.5f}; } \ No newline at end of file diff --git a/app/src/main/cpp/filter/douyin/MagicScaleFilter.cpp b/app/src/main/cpp/filter/douyin/MagicScaleFilter.cpp index 94d6022..8fbe969 100644 --- a/app/src/main/cpp/filter/douyin/MagicScaleFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicScaleFilter.cpp @@ -30,13 +30,14 @@ void MagicScaleFilter::onDestroy() { } void MagicScaleFilter::onDrawArraysPre() { - if (mFrames<=mMiddleFrames){ - mProgress = mFrames*1.0f/mMiddleFrames; - } else{ - mProgress = 2.0f-mFrames*1.0f/mMiddleFrames; + if (mFrames <= mMiddleFrames){ //根据中间帧为间隔,放大过程 + mProgress = mFrames * 1.0f /mMiddleFrames; + } else{ //缩小过程 + mProgress = 2.0f - mFrames * 1.0f /mMiddleFrames; } - setIdentityM(mMvpMatrix,0); + setIdentityM(mMvpMatrix, 0); float scale = 1.0f+0.3f*mProgress; + //正交矩阵放大 scaleM(mMvpMatrix,0,scale,scale,scale); glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix); mFrames++; diff --git a/app/src/main/cpp/filter/douyin/MagicShakeEffectFilter.cpp b/app/src/main/cpp/filter/douyin/MagicShakeEffectFilter.cpp index dc62916..ddd205f 100644 --- a/app/src/main/cpp/filter/douyin/MagicShakeEffectFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicShakeEffectFilter.cpp @@ -39,8 +39,9 @@ void MagicShakeEffectFilter::onDrawArraysPre() { mFrames = 0; } float scale= 1.0f+0.2f*mProgress; + //清空正交矩阵 setIdentityM(mMvpMatrix,0); - //设置方法百分比 + //设置正交矩阵放大,在原位置的地方放大长宽 scaleM(mMvpMatrix,0,scale,scale,1.0f); glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix); //设置色值偏移量 diff --git a/app/src/main/cpp/filter/douyin/MagicShineWhiteFilter.cpp b/app/src/main/cpp/filter/douyin/MagicShineWhiteFilter.cpp index 1a4bb19..a266960 100644 --- a/app/src/main/cpp/filter/douyin/MagicShineWhiteFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicShineWhiteFilter.cpp @@ -29,13 +29,13 @@ void MagicShineWhiteFilter::onDestroy() { } void MagicShineWhiteFilter::onDrawArraysPre() { - if (mFrames<=mMiddleFrames){ - mProgress = mFrames*1.0f/mMiddleFrames; - } else{ - mProgress = 2.0f-mFrames*1.0f/mMiddleFrames; + if (mFrames<=mMiddleFrames){ //根据中间值来增加色值 + mProgress = mFrames*1.0f /mMiddleFrames; + } else{ //减少色值 + mProgress = 2.0f-mFrames*1.0f /mMiddleFrames; } mFrames++; - if (mFrames>mMaxFrames){ + if (mFrames > mMaxFrames){ mFrames = 0; } diff --git a/app/src/main/cpp/filter/douyin/MagicSoulOutFilter.cpp b/app/src/main/cpp/filter/douyin/MagicSoulOutFilter.cpp index ca838c7..7fa87a9 100644 --- a/app/src/main/cpp/filter/douyin/MagicSoulOutFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicSoulOutFilter.cpp @@ -30,37 +30,48 @@ void MagicSoulOutFilter::onDestroy() { } void MagicSoulOutFilter::onDrawArraysPre() { - //开启颜色混合 + //存在两个图层,开启颜色混合 glEnable(GL_BLEND); //透明度混合 + // 表示源颜色乘以自身的alpha 值,目标颜色乘目标颜色值混合,如果不开启,直接被目标的画面覆盖 glBlendFunc(GL_SRC_ALPHA,GL_DST_ALPHA); - + //最大帧数mMaxFrames为15,灵魂出窍只显示15帧,设定有8帧不显示 mProgress = (float)mFrames/mMaxFrames; - if (mProgress>1){ + //当progress大于1后置0 + if (mProgress > 1.0f){ mProgress = 0; } + mFrames++; - if (mFrames>mMaxFrames + mSkipFrames){ + //skipFrames为8,23帧后置为0 + if (mFrames > mMaxFrames + mSkipFrames){ mFrames = 0; } + //setIdentityM函数移植于java中Matrix.setIdentity,初始化矩阵全置为0 setIdentityM(mMvpMatrix,0); + //第一帧没有放大,直接设置单位矩阵 glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix); + //透明度为1 float backAlpha = 1; - if (mProgress >0){ + if (mProgress > 0){ //如果是灵魂出窍效果显示中 + //计算出窍时透明度值 alpha = 0.2f - mProgress * 0.2f; - backAlpha = 1-alpha; + backAlpha = 1 - alpha; } - + //设置不显示灵魂出窍效果时,背景不透明度,不然会黑色 glUniform1f(mAlphaLocation,backAlpha); - } void MagicSoulOutFilter::onDrawArraysAfter() { - if (mProgress>0){ + if (mProgress>0){ //如果是灵魂出窍效果显示中 glUniform1f(mAlphaLocation,alpha); - float scale = 1 +mProgress; + //设置放大值 + float scale = 1 + mProgress; + //设置正交矩阵放大 scaleM(mMvpMatrix,0,scale,scale,scale); + //设置到shader里面 glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix); + //画出灵魂出校效果 glDrawArrays(GL_TRIANGLE_STRIP,0,4); } //关闭颜色混合 diff --git a/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp b/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp index 625a955..e7a01db 100644 --- a/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp @@ -32,12 +32,15 @@ void MagicVerigoFilter::onDestroy() { } void MagicVerigoFilter::onDrawPrepare() { + //绑定纹理 mRenderBuffer->bind(); glClear(GL_COLOR_BUFFER_BIT); } void MagicVerigoFilter::onDrawArraysAfter() { + //生成fbo mRenderBuffer->unbind(); + //在顶层画帧 drawCurrentFrame(); mRenderBuffer3->bind(); drawCurrentFrame(); @@ -55,8 +58,10 @@ void MagicVerigoFilter::onInit() { void MagicVerigoFilter::onInitialized() { GPUImageFilter::onInitialized(); + // mLastFrameProgram = loadProgram(readShaderFromAsset(mAssetManager,"nofilter_v.glsl")->c_str(),readShaderFromAsset(mAssetManager,"common_f.glsl")->c_str()); mCurrentFrameProgram = loadProgram(readShaderFromAsset(mAssetManager,"nofilter_v.glsl")->c_str(),readShaderFromAsset(mAssetManager,"verigo_f2.glsl")->c_str()); + //Lut纹理 mLutTexture = loadTextureFromAssetsRepeat(mAssetManager,"lookup_vertigo.png"); } diff --git a/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp b/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp index 9a81371..12fe9bc 100644 --- a/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp +++ b/app/src/main/cpp/filter/gpuimage/GpuImageFilter.cpp @@ -140,6 +140,14 @@ int GPUImageFilter::onDrawFrameFull(const GLuint textureId,GLfloat *matrix) { return ON_DRAWN; } +/** + * 滤镜使用 + * @param textureId + * @param matrix + * @param cubeBuffer + * @param textureBuffer + * @return + */ int GPUImageFilter::onDrawFrame(const GLuint textureId, GLfloat *matrix,const float *cubeBuffer, const float *textureBuffer) { onDrawPrepare(); From dbb4a023a75b502f1bb64995ff29b70b37f9c777 Mon Sep 17 00:00:00 2001 From: cangwang Date: Tue, 12 Mar 2019 21:13:38 +0800 Subject: [PATCH 25/54] =?UTF-8?q?[2019.3.12]=E6=B7=BB=E5=8A=A0=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E6=B1=A0=E7=9A=84=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/utils/MagicThreadPool.h | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/cpp/utils/MagicThreadPool.h b/app/src/main/cpp/utils/MagicThreadPool.h index 92a3004..23d0a96 100644 --- a/app/src/main/cpp/utils/MagicThreadPool.h +++ b/app/src/main/cpp/utils/MagicThreadPool.h @@ -10,21 +10,24 @@ #ifndef THREAD_POOL_H #define THREAD_POOL_H -namespace std { +namespace std { //声明std的作用域 #define MAX_THREAD_NUM = 256 class MagicThreadPool { public: + //内联函数, inline MagicThreadPool(unsigned short size = 4) : stoped{false} { + //线程池大小 idlThrNum = size < 1 ? 1 : size; for (size = 0; size < idlThrNum; ++size) { //初始化线程数量 + // 只有一次构造函数,不调用拷贝构造函数,速度最快 pool.emplace_back( [this] { // 工作线程函数 while (!this->stoped) { + //声明任务 std::function task; { // 获取一个待执行的 task - std::unique_lock lock{ - this->m_lock};// unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock() + std::unique_lock lock{this->m_lock};// unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock() this->cv_task.wait(lock, [this] { return this->stoped.load() || @@ -37,6 +40,7 @@ namespace std { this->tasks.pop(); } idlThrNum--; + //运行任务 task(); idlThrNum++; } @@ -44,8 +48,9 @@ namespace std { ); } } - + //析构函数 inline ~MagicThreadPool() { + //停止全部任务 stoped.store(true); cv_task.notify_all(); // 唤醒所有线程执行 for (std::thread &thread : pool) { @@ -55,13 +60,16 @@ namespace std { } } + //模板,需要声明类的类型,自动匹配返回值 template auto commit(F &&f, Args &&... args) -> std::future { + //直接停止任务 if (stoped.load()) // stop == true ?? throw std::runtime_error("commit on ThreadPool is stopped."); using RetType = decltype(f( args...)); // typename std::result_of::type, 函数 f 的返回值类型 + //自动返回值 auto task = std::make_shared >( std::bind(std::forward(f), std::forward(args)...) ); // wtf ! @@ -69,7 +77,7 @@ namespace std { { // 添加任务到队列 std::lock_guard lock{ m_lock};//对当前块的语句加锁 lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock() - tasks.emplace( + tasks.emplace( //放入任务到任务池 [task]() { // push(Task{...}) (*task)(); } @@ -85,8 +93,11 @@ namespace std { private: using Task = std::function; + //线程池 std::vector pool; + //任务队列 std::queue tasks; + //同步锁 std::mutex m_lock; std::condition_variable cv_task; std::atomic stoped; From a55a19dd39a52cf619f131902ebde2c7e271da8b Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 14 Mar 2019 15:29:23 +0800 Subject: [PATCH 26/54] =?UTF-8?q?[2019.3.14]=E6=B7=BB=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/verigo_f2.glsl | 4 ++++ .../cpp/filter/douyin/MagicVerigoFilter.cpp | 24 ++++++++++++++++--- .../main/cpp/filter/douyin/RenderBuffer.cpp | 20 +++++++++------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/app/src/main/assets/verigo_f2.glsl b/app/src/main/assets/verigo_f2.glsl index cde79ac..cf81710 100644 --- a/app/src/main/assets/verigo_f2.glsl +++ b/app/src/main/assets/verigo_f2.glsl @@ -7,6 +7,7 @@ uniform sampler2D lookupTable; // 颜色查找表纹理 out vec4 glFragColor; +//固定的Lut纹理对换计算 vec4 getLutColor(vec4 textureColor,sampler2D lookupTexture){ float blueColor = textureColor.b * 63.0; @@ -36,7 +37,10 @@ vec4 getLutColor(vec4 textureColor,sampler2D lookupTexture){ } void main(){ + //上一帧纹理 vec4 lastFrame = texture(inputTextureLast,textureCoordinate); + //此帧对应的Lut转换纹理 vec4 currentFrame = getLutColor(texture(inputImageTexture,textureCoordinate),lookupTable); + //上一帧和此帧混色处理 glFragColor = vec4(0.95 * lastFrame.r + 0.05* currentFrame.r,currentFrame.g * 0.2 + lastFrame.g * 0.8, currentFrame.b,1.0); } \ No newline at end of file diff --git a/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp b/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp index e7a01db..0f0634c 100644 --- a/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp +++ b/app/src/main/cpp/filter/douyin/MagicVerigoFilter.cpp @@ -38,15 +38,22 @@ void MagicVerigoFilter::onDrawPrepare() { } void MagicVerigoFilter::onDrawArraysAfter() { - //生成fbo + //将摄像头的数据保存到mRenderBuffer的fbo中 mRenderBuffer->unbind(); - //在顶层画帧 + + //在顶层画帧,真正画显示的画面 drawCurrentFrame(); + mRenderBuffer3->bind(); + //绘制当前帧 drawCurrentFrame(); + //将当前帧保存到生成mRenderBuffer3的fbo mRenderBuffer3->unbind(); + mRenderBuffer2->bind(); + //使用mRenderBuffer的fbo,再绘制mRenderBuffer2的纹理fbo中 drawToBuffer(); + //生成mRenderBuffer2的fbo,用于下一帧的绘制 mRenderBuffer2->unbind(); mFirst = false; } @@ -58,8 +65,9 @@ void MagicVerigoFilter::onInit() { void MagicVerigoFilter::onInitialized() { GPUImageFilter::onInitialized(); - // + //用于上一帧数据,用于下一帧 mLastFrameProgram = loadProgram(readShaderFromAsset(mAssetManager,"nofilter_v.glsl")->c_str(),readShaderFromAsset(mAssetManager,"common_f.glsl")->c_str()); + //此帧使用的绘制program mCurrentFrameProgram = loadProgram(readShaderFromAsset(mAssetManager,"nofilter_v.glsl")->c_str(),readShaderFromAsset(mAssetManager,"verigo_f2.glsl")->c_str()); //Lut纹理 mLutTexture = loadTextureFromAssetsRepeat(mAssetManager,"lookup_vertigo.png"); @@ -68,12 +76,14 @@ void MagicVerigoFilter::onInitialized() { void MagicVerigoFilter::onInputSizeChanged(const int width, const int height) { mScreenWidth = width; mScreenHeight = height; + //建立三个fbo纹理 mRenderBuffer = new RenderBuffer(GL_TEXTURE8,width,height); mRenderBuffer2 = new RenderBuffer(GL_TEXTURE9,width,height); mRenderBuffer3 = new RenderBuffer(GL_TEXTURE10,width,height); } void MagicVerigoFilter::drawToBuffer() { + glUseProgram(mLastFrameProgram); GLint position = glGetAttribLocation(mLastFrameProgram,"position"); GLint texcoord = glGetAttribLocation(mLastFrameProgram,"inputTextureCoordinate"); @@ -84,14 +94,17 @@ void MagicVerigoFilter::drawToBuffer() { glVertexAttribPointer(texcoord,2,GL_FLOAT,GL_FALSE,0,FULL_RECTANGLE_TEX_COORDS); glUniformMatrix4fv(matrix,1,GL_FALSE,NONE_MATRIX); + //保存到mRenderBuffer3中的fbo,用于下一帧 GLint textureLocation0 = glGetUniformLocation(mLastFrameProgram,"inputImageTexture"); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D,mRenderBuffer3->getTextureId()); glUniform1i(textureLocation0,3); glClear(GL_COLOR_BUFFER_BIT); + //绘制 glDrawArrays(GL_TRIANGLE_STRIP,0,4); + //关闭纹理 glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D,mRenderBuffer3->getTextureId()); glActiveTexture(GL_TEXTURE0); @@ -108,27 +121,32 @@ void MagicVerigoFilter::drawCurrentFrame() { glVertexAttribPointer(texcoord,2,GL_FLOAT,GL_FALSE,0,FULL_RECTANGLE_TEX_COORDS); glUniformMatrix4fv(matrix,1,GL_FALSE,NONE_MATRIX); + //绑定此帧纹理到mRenderBuffer(摄像头原始数据) GLint textureLocation0 = glGetUniformLocation(mCurrentFrameProgram,"inputImageTexture"); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D,mRenderBuffer->getTextureId()); glUniform1i(textureLocation0,3); + //如果是第一帧绑定为mRenderBuffer的fbo,除外绑定mRenderBuffer2的fbo GLint textureLocation1 = glGetUniformLocation(mCurrentFrameProgram,"inputTextureLast"); glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D,mFirst ? mRenderBuffer->getTextureId() : mRenderBuffer2->getTextureId()); glUniform1i(textureLocation1,4); + //使用Lut纹理 GLint textureLocation2 = glGetUniformLocation(mCurrentFrameProgram,"lookupTable"); glActiveTexture(GL_TEXTURE5); glBindTexture(GL_TEXTURE_2D,mLutTexture); glUniform1i(textureLocation2,5); glClear(GL_COLOR_BUFFER_BIT); + //画图 glDrawArrays(GL_TRIANGLE_STRIP,0,4); glDisableVertexAttribArray(position); glDisableVertexAttribArray(texcoord); + //关闭纹理 glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D,mRenderBuffer->getTextureId()); glActiveTexture(GL_TEXTURE0); diff --git a/app/src/main/cpp/filter/douyin/RenderBuffer.cpp b/app/src/main/cpp/filter/douyin/RenderBuffer.cpp index 9c33766..05f3180 100644 --- a/app/src/main/cpp/filter/douyin/RenderBuffer.cpp +++ b/app/src/main/cpp/filter/douyin/RenderBuffer.cpp @@ -17,8 +17,10 @@ RenderBuffer::RenderBuffer(){ RenderBuffer::RenderBuffer(GLenum activeTextureUnit, int width, int height) { mWidth = width; mHeight = height; + //激活纹理插槽 glActiveTexture(activeTextureUnit); // mTextureId = get2DTextureRepeatID(); + //纹理id mTextureId = get2DTextureID(); // unsigned char* texBuffer = (unsigned char*)malloc(sizeof(unsigned char*) * width * height * 4); // glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,texBuffer); @@ -27,37 +29,38 @@ RenderBuffer::RenderBuffer(GLenum activeTextureUnit, int width, int height) { glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); - + //生成fb的id glGenFramebuffers(1,&mFrameBufferId); glBindFramebuffer(GL_FRAMEBUFFER,mFrameBufferId); - + //生成渲染缓冲区id glGenRenderbuffers(1,&mRenderBufferId); glBindRenderbuffer(GL_RENDERBUFFER,mRenderBufferId); //指定存储在 renderbuffer 中图像的宽高以及颜色格式,并按照此规格为之分配存储空间 glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT16,width,height); - + //复位 glBindFramebuffer(GL_FRAMEBUFFER,0); glBindRenderbuffer(GL_RENDERBUFFER,0); } void RenderBuffer::bind() { - checkGLError("RenderBuffer"); + //清空视口 glViewport(0,0,mWidth,mHeight); - checkGLError("glViewport"); + //绑定fb的纹理id glBindFramebuffer(GL_FRAMEBUFFER,mFrameBufferId); - checkGLError("glBindFramebuffer"); + //绑定2D纹理关联到fbo glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,mTextureId,0); - checkGLError("glFramebufferTexture2D"); + //绑定fo纹理到渲染缓冲区对象 glBindRenderbuffer(GL_RENDERBUFFER,mRenderBufferId); //将渲染缓冲区作为深度缓冲区附加到fbo glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,mRenderBufferId); - checkGLError("glFramebufferRenderbuffer"); + //检查fbo的状态 if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){ ALOGE("framebuffer error"); } } void RenderBuffer::unbind() { + //移除绑定 glBindFramebuffer(GL_FRAMEBUFFER,0); glBindRenderbuffer(GL_RENDERBUFFER,0); // glActiveTexture(GL_TEXTURE0); @@ -69,6 +72,7 @@ GLuint RenderBuffer::getTextureId() { RenderBuffer::~RenderBuffer() { + //释放纹理资源 glDeleteBuffers(1,&mTextureId); glDeleteFramebuffers(1,&mFrameBufferId); glDeleteRenderbuffers(1,&mRenderBufferId); From 461a3c942a6fc4fb23bb3ad8ef4a493627f99ead Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 14 Mar 2019 16:05:18 +0800 Subject: [PATCH 27/54] =?UTF-8?q?[2019.3.14]=E6=B7=BB=E5=8A=A0=E4=B8=89?= =?UTF-8?q?=E9=87=8D=E9=95=9C=E5=83=8F=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/CMakeLists.txt | 1 + app/src/main/assets/three_win.glsl | 20 ++++++++ .../main/cpp/filter/MagicFilterFactory.cpp | 4 ++ app/src/main/cpp/filter/MagicFilterFactory.h | 1 + .../cpp/filter/douyin/MagicThreeWinFilter.cpp | 50 +++++++++++++++++++ .../cpp/filter/douyin/MagicThreeWinFilter.h | 34 +++++++++++++ .../magic/helper/FilterTypeHelper.java | 4 ++ .../magic/helper/MagicFilterType.java | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 10 files changed, 117 insertions(+) create mode 100644 app/src/main/assets/three_win.glsl create mode 100644 app/src/main/cpp/filter/douyin/MagicThreeWinFilter.cpp create mode 100644 app/src/main/cpp/filter/douyin/MagicThreeWinFilter.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6cba220..0ab91ea 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -101,6 +101,7 @@ add_library( # Sets the name of the library. src/main/cpp/filter/douyin/MagicShakeEffectFilter.cpp src/main/cpp/filter/douyin/MagicShineWhiteFilter.cpp src/main/cpp/filter/douyin/MagicVerigoFilter.cpp + src/main/cpp/filter/douyin/MagicThreeWinFilter.cpp src/main/cpp/filter/douyin/RenderBuffer.cpp) # Searches for a specified prebuilt library and stores the path as a diff --git a/app/src/main/assets/three_win.glsl b/app/src/main/assets/three_win.glsl new file mode 100644 index 0000000..1385436 --- /dev/null +++ b/app/src/main/assets/three_win.glsl @@ -0,0 +1,20 @@ +#version 300 es +precision mediump float; + + in vec2 textureCoordinate; + uniform sampler2D inputImageTexture; + out vec4 glFragColor; + + void main() + { + highp float y; + if(textureCoordinate.y>=0.0 &&textureCoordinate.y<=0.33){ + y = textureCoordinate.y + 0.33; + glFragColor = texture(inputImageTexture,vec2(textureCoordinate.x,y)); + }else if(textureCoordinate.y>0.33 &&textureCoordinate.y<=0.66){ + glFragColor = texture(inputImageTexture,textureCoordinate); + }else{ + y = textureCoordinate.y - 0.33; + glFragColor = texture(inputImageTexture,vec2(textureCoordinate.x,y)); + } + } \ No newline at end of file diff --git a/app/src/main/cpp/filter/MagicFilterFactory.cpp b/app/src/main/cpp/filter/MagicFilterFactory.cpp index 82a8a2a..0e7fc4d 100644 --- a/app/src/main/cpp/filter/MagicFilterFactory.cpp +++ b/app/src/main/cpp/filter/MagicFilterFactory.cpp @@ -47,6 +47,7 @@ #include "src/main/cpp/filter/douyin/MagicShineWhiteFilter.h" #include "src/main/cpp/filter/douyin/MagicShakeEffectFilter.h" #include "src/main/cpp/filter/douyin/MagicVerigoFilter.h" +#include "src/main/cpp/filter/douyin/MagicThreeWinFilter.h" #include "src/main/cpp/utils/OpenglUtils.h" #define LOG_TAG "MagicFilterFactory" @@ -145,6 +146,8 @@ GPUImageFilter* initFilters(int type,AAssetManager* assetManager){ return new MagicSweetsFilter(assetManager); case TENDER: return new MagicTenderFilter(assetManager); + case THREEWIN: + return new MagicThreeWinFilter(assetManager); case TOASTER2: return new MagicToasterFilter(assetManager); case VALENCIA: @@ -173,6 +176,7 @@ int* getFilterTypes(int &len){ SCALE, SHINEWHITE, VERIGO, + THREEWIN, FAIRYTALE, SUNRISE, SUNSET, diff --git a/app/src/main/cpp/filter/MagicFilterFactory.h b/app/src/main/cpp/filter/MagicFilterFactory.h index 176c52f..38127a2 100644 --- a/app/src/main/cpp/filter/MagicFilterFactory.h +++ b/app/src/main/cpp/filter/MagicFilterFactory.h @@ -17,6 +17,7 @@ enum MagicFilterType{ SCALE, SHINEWHITE, VERIGO, + THREEWIN, FAIRYTALE, SUNRISE, SUNSET, diff --git a/app/src/main/cpp/filter/douyin/MagicThreeWinFilter.cpp b/app/src/main/cpp/filter/douyin/MagicThreeWinFilter.cpp new file mode 100644 index 0000000..b89f962 --- /dev/null +++ b/app/src/main/cpp/filter/douyin/MagicThreeWinFilter.cpp @@ -0,0 +1,50 @@ +#include "MagicThreeWinFilter.h" +#include "src/main/cpp/utils/OpenglUtils.h" +#include "src/main/cpp/utils/Matrix.h" + +#define LOG_TAG "MagicThreeWinFilter" +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) + + +#define GET_ARRAY_LEN(array,len){len = (sizeof(array) / sizeof(array[0]));} + +/** + * cangwang 2019.1.4 + * 三重影像 + */ +MagicThreeWinFilter::MagicThreeWinFilter(){ + +} + +MagicThreeWinFilter::MagicThreeWinFilter(AAssetManager *assetManager) + : GPUImageFilter(assetManager,readShaderFromAsset(assetManager,"soulout_v.glsl"), readShaderFromAsset(assetManager,"three_win.glsl")),mMvpMatrix(new float[16]){ + +} + +MagicThreeWinFilter::~MagicThreeWinFilter() { + free(mMvpMatrix); +} + +void MagicThreeWinFilter::onDestroy() { +} + +void MagicThreeWinFilter::onDrawArraysPre() { + setIdentityM(mMvpMatrix, 0); + glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix); +} + +void MagicThreeWinFilter::onDrawArraysAfter() { + +} + + +void MagicThreeWinFilter::onInit() { + GPUImageFilter::onInit(); + mMvpMatrixLocation = glGetUniformLocation(mGLProgId,"uMvpMatrix"); +} + +void MagicThreeWinFilter::onInitialized() { + GPUImageFilter::onInitialized(); + +} \ No newline at end of file diff --git a/app/src/main/cpp/filter/douyin/MagicThreeWinFilter.h b/app/src/main/cpp/filter/douyin/MagicThreeWinFilter.h new file mode 100644 index 0000000..e7d6514 --- /dev/null +++ b/app/src/main/cpp/filter/douyin/MagicThreeWinFilter.h @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include +#include "src/main/cpp/filter/gpuimage/GpuImageFilter.h" + +/** + * cangwang 2019.1.4 + */ +class MagicThreeWinFilter: public GPUImageFilter{ + +public: + MagicThreeWinFilter(); + MagicThreeWinFilter(AAssetManager *assetManager); + ~MagicThreeWinFilter(); + void onDestroy() override ; + +protected: + void onInit() override; + void onInitialized() override ; + void onDrawArraysPre() override; + void onDrawArraysAfter() override; + +private: + float mProgress; + float* mMvpMatrix; + int mFrames=0; + int mMaxFrames = 14; + int mMiddleFrames = mMaxFrames/2; + GLint mMvpMatrixLocation; + float alpha; +}; \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java b/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java index 0d626b6..90e2f08 100644 --- a/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java +++ b/app/src/main/java/com/cangwang/magic/helper/FilterTypeHelper.java @@ -82,6 +82,8 @@ public static int FilterType2Thumb(int type){ return R.drawable.glitch; case VERIGO: return R.drawable.verigo; + case THREEWIN: + return R.drawable.soulout; case WHITECAT: return R.drawable.filter_thumb_whitecat; case BLACKCAT: @@ -186,6 +188,8 @@ public static int FilterType2Name(int type){ return R.string.filter_glitch; case VERIGO: return R.string.filter_verigo; + case THREEWIN: + return R.string.filter_three_win; case WHITECAT: return R.string.filter_whitecat; case BLACKCAT: diff --git a/app/src/main/java/com/cangwang/magic/helper/MagicFilterType.java b/app/src/main/java/com/cangwang/magic/helper/MagicFilterType.java index ee1e7ab..c25ac4e 100644 --- a/app/src/main/java/com/cangwang/magic/helper/MagicFilterType.java +++ b/app/src/main/java/com/cangwang/magic/helper/MagicFilterType.java @@ -11,6 +11,7 @@ public enum MagicFilterType { SCALE, SHINEWHITE, VERIGO, + THREEWIN, FAIRYTALE, SUNRISE, SUNSET, diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 20a085c..74ed294 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -7,6 +7,7 @@ 放大 闪白 幻觉 + 三重镜像 白猫 黑猫 浪漫 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df6df18..ebd5317 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Glitch Scale ShineWhite + ThreeWin Verigo Amaro Brannan From 008f0178317d72b009d1dd74fe8e43b2c234444d Mon Sep 17 00:00:00 2001 From: cangwang <284699931@qq.com> Date: Thu, 21 Mar 2019 19:49:45 +0800 Subject: [PATCH 28/54] =?UTF-8?q?[2019.3.21]=E5=8D=87=E7=BA=A7=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/gradle.xml | 1 + .idea/modules.xml | 1 + app/build.gradle | 14 +++++---- app/src/main/res/values/strings.xml | 1 + build.gradle | 6 ++-- gradle/wrapper/gradle-wrapper.properties | 4 +-- pickphotoview/build.gradle | 29 +++++++++---------- .../com/werb/pickphotoview/ui/GridFragment.kt | 4 +-- player/.gitignore | 1 + player/build.gradle | 21 ++++++++++++++ player/src/main/AndroidManifest.xml | 12 ++++++++ settings.gradle | 2 +- 12 files changed, 67 insertions(+), 29 deletions(-) create mode 100644 player/.gitignore create mode 100644 player/build.gradle create mode 100644 player/src/main/AndroidManifest.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml index fc3cdb7..648cd9b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -10,6 +10,7 @@ \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt index e2e6719..23b202b 100644 --- a/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt +++ b/app/src/main/java/com/cangwang/magic/CameraFilterV2Activity.kt @@ -6,7 +6,6 @@ import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.app.AlertDialog import android.content.pm.ActivityInfo -import android.content.res.AssetManager import android.graphics.Point import android.os.Bundle import android.support.v7.app.AppCompatActivity diff --git a/app/src/main/java/com/cangwang/magic/CameraSampleActivity.kt b/app/src/main/java/com/cangwang/magic/CameraSampleActivity.kt new file mode 100755 index 0000000..d605d3c --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/CameraSampleActivity.kt @@ -0,0 +1,154 @@ +/* + * GPUImage-x + * + * Copyright (C) 2017 Yijin Wang, Yiqian Wang + * + * 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. + */ + +package com.cangwang.magic + +import android.app.Activity +import android.graphics.Bitmap +import android.media.MediaScannerConnection +import android.os.Bundle +import android.os.Environment +import android.view.View +import android.widget.SeekBar +import android.widget.Toast + +import com.cangwang.gpuimage.GPUImage +import com.cangwang.gpuimage.GPUImageFilter +import com.cangwang.gpuimage.GPUImageSource +import com.cangwang.gpuimage.GPUImageSourceCamera +import com.cangwang.gpuimage.GPUImageView +import com.cangwang.magic.util.FilterHelper +import kotlinx.android.synthetic.main.activity_camera_sample.* + +import java.io.File +import java.io.FileNotFoundException +import java.io.FileOutputStream + +class CameraSampleActivity : Activity(), View.OnClickListener, SeekBar.OnSeekBarChangeListener, + FilterHelper.OnFilterSelectedListener { + + private var sourceCamera: GPUImageSourceCamera? = null + private var filter: GPUImageFilter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_camera_sample) + + btn_flip_cam.setOnClickListener(this) + btn_filter.setOnClickListener(this) + btn_capture.setOnClickListener(this) + + val seekBar = findViewById(R.id.seek) as SeekBar + seekBar.setOnSeekBarChangeListener(this) + seekBar.progress = 50 + + sourceCamera = GPUImageSourceCamera(this@CameraSampleActivity) + filter = GPUImageFilter.create("BrightnessFilter") + filter?.let { + sourceCamera?.addTarget(it) + ?.addTarget(findViewById(R.id.gpuimagexview) as GPUImageView) + } + sourceCamera?.let { + GPUImage.setSource(it) + } + } + + override fun onClick(v: View) { + when (v.id) { + R.id.btn_flip_cam -> if (sourceCamera != null) { + sourceCamera!!.switchCamera() + } + R.id.btn_filter -> FilterHelper.showListDialog(this, this) + R.id.btn_capture -> sourceCamera?.captureAProcessedFrameData(filter, + object : GPUImageSource.ProcessedFrameDataCallback { + override fun onResult(result: Bitmap?) { + if (result != null) { + val path = Environment + .getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES) + val file = File(path, + "gpuimage-x" + "/" + System.currentTimeMillis() + ".jpg") + try { + file.parentFile.mkdirs() + result.compress(Bitmap.CompressFormat.JPEG, 80, + FileOutputStream(file)) + + // make a toast + runOnUiThread { + Toast.makeText(this@CameraSampleActivity, + "Image Saved:" + file.absolutePath, + Toast.LENGTH_SHORT).show() + } + + // Tell the media scanner about the new file, + // so that it is immediately present in your album. + MediaScannerConnection.scanFile(this@CameraSampleActivity, + arrayOf(file.toString()), null, null) + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + } + } + }) + else -> { + } + } + } + + override fun OnFilterSelected(newFilter: GPUImageFilter) { + filter?.let { + sourceCamera?.removeTarget(it) + it.destroy() // destroy instance if you want + } + sourceCamera?.addTarget(newFilter) + ?.addTarget(findViewById(R.id.gpuimagexview) as GPUImageView) + filter = newFilter + //sourceCamera.proceed(); + } + + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (sourceCamera != null && filter != null) { + val value = progress / 100.0f // let the value between 0 and 1 + FilterHelper.applyFilterWithSliderValue(filter!!, value) + //sourceCamera.proceed(); + } + } + + override fun onResume() { + super.onResume() + if (sourceCamera != null) { + sourceCamera!!.onResume() + } + } + + override fun onPause() { + if (sourceCamera != null) { + sourceCamera!!.onPause() + } + super.onPause() + } + + override fun onDestroy() { + GPUImage.destroy() + super.onDestroy() + } + + override fun onStartTrackingTouch(seekBar: SeekBar) {} + + override fun onStopTrackingTouch(seekBar: SeekBar) {} +} diff --git a/app/src/main/java/com/cangwang/magic/MainActivity.kt b/app/src/main/java/com/cangwang/magic/MainActivity.kt index 82ab4b1..c7f9465 100644 --- a/app/src/main/java/com/cangwang/magic/MainActivity.kt +++ b/app/src/main/java/com/cangwang/magic/MainActivity.kt @@ -5,60 +5,93 @@ import android.content.Intent import android.content.pm.PackageManager import android.support.v7.app.AppCompatActivity import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.support.v4.app.ActivityCompat import android.support.v4.content.PermissionChecker import android.widget.Toast +import com.cangwang.magic.util.CorountinesUtil import com.werb.pickphotoview.PickPhotoView import com.werb.pickphotoview.model.SelectModel import com.werb.pickphotoview.util.PickConfig import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.newFixedThreadPoolContext +import kotlinx.coroutines.runBlocking import java.util.ArrayList +import kotlin.coroutines.CoroutineContext -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), CoroutineScope by CoroutineScope(Dispatchers.Default) { companion object { const val CAMERA_REQ = 1 const val CAMERA_FILTER = 2 - const val ALBUM_REQ =3 + const val ALBUM_REQ = 3 + const val CAMERA_EFFECT = 4 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button_camera.setOnClickListener { v -> - if (PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(this@MainActivity, arrayOf(Manifest.permission.CAMERA), CAMERA_REQ) + if (PermissionChecker.checkSelfPermission(this@MainActivity, + Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { + ActivityCompat.requestPermissions(this@MainActivity, + arrayOf(Manifest.permission.CAMERA), CAMERA_REQ) } else { startActivity(CAMERA_REQ) } } button_filter.setOnClickListener { - if (PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED || - PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED || - PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(this@MainActivity, arrayOf(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO), CAMERA_FILTER) + if (PermissionChecker.checkSelfPermission(this@MainActivity, + Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED || + PermissionChecker.checkSelfPermission(this@MainActivity, + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED || + PermissionChecker.checkSelfPermission(this@MainActivity, + Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED) { + ActivityCompat.requestPermissions(this@MainActivity, + arrayOf(Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.RECORD_AUDIO), CAMERA_FILTER) } else { startActivity(CAMERA_FILTER) } } button_album.setOnClickListener { - if (PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(this@MainActivity, arrayOf(Manifest.permission.CAMERA), ALBUM_REQ) + if (PermissionChecker.checkSelfPermission(this@MainActivity, + Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { + ActivityCompat.requestPermissions(this@MainActivity, + arrayOf(Manifest.permission.CAMERA), ALBUM_REQ) } else { startActivity(ALBUM_REQ) } } + button_effect.setOnClickListener { + if (PermissionChecker.checkSelfPermission(this@MainActivity, + Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { + ActivityCompat.requestPermissions(this@MainActivity, + arrayOf(Manifest.permission.CAMERA), CAMERA_EFFECT) + } else { + startActivity(CAMERA_EFFECT) + } + } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, - grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, + grantResults: IntArray + ) { if (requestCode == CAMERA_REQ && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startActivity(CAMERA_REQ) - }else if (requestCode == CAMERA_FILTER && grantResults[0] == PackageManager.PERMISSION_GRANTED + } else if (requestCode == CAMERA_FILTER && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED && grantResults[2] == PackageManager.PERMISSION_GRANTED) { startActivity(CAMERA_FILTER) - }else if (requestCode == ALBUM_REQ && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startActivity(ALBUM_REQ) + } else if (requestCode == ALBUM_REQ && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + startActivity(ALBUM_REQ) } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults) } @@ -67,11 +100,12 @@ class MainActivity : AppCompatActivity() { private fun startActivity(id: Int) { when (id) { CAMERA_REQ -> startActivity(Intent(this, CameraActivity::class.java)) - CAMERA_FILTER -> startActivity(Intent(this,CameraFilterV2Activity::class.java)) - ALBUM_REQ ->{ + CAMERA_FILTER -> startActivity(Intent(this, CameraFilterV2Activity::class.java)) + ALBUM_REQ -> { PickPhotoView.Builder(this@MainActivity) .setPickPhotoSize(1) - .setClickSelectable(true) // click one image immediately close and return image + .setClickSelectable( + true) // click one image immediately close and return image .setShowCamera(true) .setHasPhotoSize(7) .setAllPhotoSize(10) @@ -81,6 +115,7 @@ class MainActivity : AppCompatActivity() { .setShowVideo(false) .start() } + CAMERA_EFFECT -> startActivity(Intent(this, CameraSampleActivity::class.java)) else -> { } } @@ -95,13 +130,14 @@ class MainActivity : AppCompatActivity() { return } if (requestCode == PickConfig.PICK_PHOTO_DATA) { - val selectPaths = data.getSerializableExtra(PickConfig.INTENT_IMG_LIST_SELECT) as ArrayList - if (selectPaths.size>0) { + val selectPaths = data.getSerializableExtra( + PickConfig.INTENT_IMG_LIST_SELECT) as ArrayList + if (selectPaths.size > 0) { val intent = Intent(this, ImageEditActivity::class.java) intent.putExtra(PickConfig.INTENT_IMG_LIST_SELECT, selectPaths[0]) startActivity(intent) - }else{ - Toast.makeText(this,"choose no picture",Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this, "choose no picture", Toast.LENGTH_SHORT).show() } } } diff --git a/app/src/main/java/com/cangwang/magic/TriangleActivity.kt b/app/src/main/java/com/cangwang/magic/TriangleActivity.kt index ef1b1fb..9204ce8 100644 --- a/app/src/main/java/com/cangwang/magic/TriangleActivity.kt +++ b/app/src/main/java/com/cangwang/magic/TriangleActivity.kt @@ -8,10 +8,10 @@ import com.cangwang.magic.util.RenderJNI /** * Created by zjl on 2018/10/12. */ -class TriangleActivity:AppCompatActivity(){ +class TriangleActivity : AppCompatActivity() { - lateinit var mGLSurfaceView:GLSurfaceView - lateinit var mRenderer:RenderJNI + lateinit var mGLSurfaceView: GLSurfaceView + lateinit var mRenderer: RenderJNI override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -32,6 +32,4 @@ class TriangleActivity:AppCompatActivity(){ super.onPause() mGLSurfaceView.onPause() } - - } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/util/CorountinesUtil.kt b/app/src/main/java/com/cangwang/magic/util/CorountinesUtil.kt new file mode 100644 index 0000000..4e0c74c --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/util/CorountinesUtil.kt @@ -0,0 +1,7 @@ +package com.cangwang.magic.util + +import kotlinx.coroutines.newFixedThreadPoolContext + +object CorountinesUtil { + val Background = newFixedThreadPoolContext(2, "bg") +} \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/util/FilterHelper.kt b/app/src/main/java/com/cangwang/magic/util/FilterHelper.kt new file mode 100755 index 0000000..c1cdae1 --- /dev/null +++ b/app/src/main/java/com/cangwang/magic/util/FilterHelper.kt @@ -0,0 +1,294 @@ +/* + * GPUImage-x + * + * Copyright (C) 2017 Yijin Wang, Yiqian Wang + * + * 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. + */ + +package com.cangwang.magic.util + +import android.app.AlertDialog +import android.content.Context +import android.widget.Toast +import com.cangwang.gpuimage.GPUImageFilter + +import java.util.LinkedList + +object FilterHelper { + internal val FILTER_BRIGHTNESS = 0 + internal val FILTER_COLOR_INVERT = 1 + internal val FILTER_GRAYSCALE = 2 + internal val FILTER_GAUSSIAN_BLUR = 3 + internal val FILTER_BILATERAL = 4 + internal val FILTER_IOS_BLUR = 5 + internal val FILTER_CANNY_EDGE_DETECTION = 6 + internal val FILTER_WEAK_PIXEL_INCLUSION = 7 + internal val FILTER_NON_MAXIMUM_SUPPRESSION = 8 + internal val FILTER_BEAUTIFY = 9 + internal val FILTER_SOBEL_EDGE_DETECTION = 10 + internal val FILTER_SKETCH = 11 + internal val FILTER_TOON = 12 + internal val FILTER_SMOOTH_TOON = 13 + internal val FILTER_POSTERIZE = 14 + internal val FILTER_PIXELLATION = 15 + internal val FILTER_SATURATION = 16 + internal val FILTER_CONTRAST = 17 + internal val FILTER_EXPOSURE = 18 + internal val FILTER_RGB = 19 + internal val FILTER_HUE = 20 + internal val FILTER_WHITE_BALANCE = 21 + internal val FILTER_LUMINANCE_RANGE = 22 + internal val FILTER_EMBOSS = 23 + internal val FILTER_HALFTONE = 24 + internal val FILTER_CROSSHATCH = 25 + internal val FILTER_SPHERE_REFRACTION = 26 + internal val FILTER_GLASS_SPHERE = 27 + + private var filterList: FilterList? = null + + private fun getFilterList(): FilterList { + if (filterList == null) { + filterList = FilterList() + filterList!!.addFilter(FILTER_BRIGHTNESS, "Brightness", "BrightnessFilter") + filterList!!.addFilter(FILTER_COLOR_INVERT, "Color Invert", "ColorInvertFilter") + filterList!!.addFilter(FILTER_GRAYSCALE, "Grayscale", "GrayscaleFilter") + filterList!!.addFilter(FILTER_GAUSSIAN_BLUR, "Gaussian Blur", "GaussianBlurFilter") + filterList!!.addFilter(FILTER_BILATERAL, "Bilateral", "BilateralFilter") + filterList!!.addFilter(FILTER_IOS_BLUR, "iOS 7 Blur", "IOSBlurFilter") + filterList!!.addFilter(FILTER_CANNY_EDGE_DETECTION, "Canny Edge Detection", + "CannyEdgeDetectionFilter") + filterList!!.addFilter(FILTER_WEAK_PIXEL_INCLUSION, "Weak Pixel Inclusion", + "WeakPixelInclusionFilter") + filterList!!.addFilter(FILTER_NON_MAXIMUM_SUPPRESSION, "Non Maximum Suppression", + "NonMaximumSuppressionFilter") + filterList!!.addFilter(FILTER_BEAUTIFY, "Beautify", "BeautifyFilter") + filterList!!.addFilter(FILTER_SOBEL_EDGE_DETECTION, "Sobel Edge Detection", + "SobelEdgeDetectionFilter") + filterList!!.addFilter(FILTER_SKETCH, "Sketch", "SketchFilter") + filterList!!.addFilter(FILTER_TOON, "Toon", "ToonFilter") + filterList!!.addFilter(FILTER_SMOOTH_TOON, "Smooth Toon", "SmoothToonFilter") + filterList!!.addFilter(FILTER_POSTERIZE, "Posterize", "PosterizeFilter") + filterList!!.addFilter(FILTER_PIXELLATION, "Pixellation", "PixellationFilter") + filterList!!.addFilter(FILTER_SATURATION, "Saturation", "SaturationFilter") + filterList!!.addFilter(FILTER_CONTRAST, "Contrast", "ContrastFilter") + filterList!!.addFilter(FILTER_EXPOSURE, "Exposure", "ExposureFilter") + filterList!!.addFilter(FILTER_RGB, "RGB Adjustment", "RGBFilter") + filterList!!.addFilter(FILTER_HUE, "Hue Adjustment", "HueFilter") + filterList!!.addFilter(FILTER_WHITE_BALANCE, "White Balance", "WhiteBalanceFilter") + filterList!!.addFilter(FILTER_LUMINANCE_RANGE, "Luminance Range", + "LuminanceRangeFilter") + filterList!!.addFilter(FILTER_EMBOSS, "Emboss Filter", "EmbossFilter") + filterList!!.addFilter(FILTER_HALFTONE, "Halftone Filter", "HalftoneFilter") + filterList!!.addFilter(FILTER_CROSSHATCH, "Crosshatch Filter", "CrosshatchFilter") + filterList!!.addFilter(FILTER_SPHERE_REFRACTION, "Sphere Refraction Filter", + "SphereRefractionFilter") + filterList!!.addFilter(FILTER_GLASS_SPHERE, "Glass Sphere Filter", "GlassSphereFilter") + } + return filterList!! + } + + fun showListDialog( + context: Context, + listener: OnFilterSelectedListener + ) { + + val filters = getFilterList() + val builder = AlertDialog.Builder(context) + builder.setTitle("Choose a filter") + builder.setItems(filters.displayNames.toTypedArray() + ) { dialog, item -> + Toast.makeText(context, filters.displayNames[item], + Toast.LENGTH_SHORT).show() + listener.OnFilterSelected(createFilter(filters.filters[item])) + } + builder.create().show() + } + + fun applyFilterWithSliderValue(filter: GPUImageFilter, v: Float) { + var value = v + val filters = getFilterList() + val filterClassName = filter.filterClassName + when (filters.classNames.indexOf(filterClassName)) { + FILTER_BRIGHTNESS -> { + run { + // let the value between -1 and 1 + value = (value - 0.5f) * 2.0f + filter.setProperty("brightness", value) + } + } + FILTER_COLOR_INVERT -> { + run { } + } + FILTER_GRAYSCALE -> { + run { } + } + FILTER_GAUSSIAN_BLUR -> { + run { + // let the value between 0 - 24 + value *= 24.0f + //filter->setProperty("radius", (int)value); + filter.setProperty("sigma", value) + } + } + FILTER_IOS_BLUR -> { + run { + value = value * 10.0f + 1.0f + filter.setProperty("downSampling", value) + } + } + FILTER_BILATERAL -> { + run { + // let the value between 0 - 10 + value *= 10.0f + filter.setProperty("distanceNormalizationFactor", value) + } + } + FILTER_CANNY_EDGE_DETECTION -> { + run { + //todo + } + } + FILTER_WEAK_PIXEL_INCLUSION -> { + run { } + } + FILTER_NON_MAXIMUM_SUPPRESSION -> { + run { + value *= 5.0f + filter.setProperty("texelSizeMultiplier", value) + } + } + FILTER_BEAUTIFY -> { + run { } + } + FILTER_SOBEL_EDGE_DETECTION -> { + run { filter.setProperty("edgeStrength", value) } + } + FILTER_SKETCH -> { + run { filter.setProperty("edgeStrength", value) } + } + FILTER_TOON -> { + run { + value *= 20.0f + filter.setProperty("quantizationLevels", value) + } + } + FILTER_SMOOTH_TOON -> { + run { + // let the value between 1 - 6 + value = value * 5.0f + 1.0f + filter.setProperty("blurRadius", value.toInt()) + } + } + FILTER_POSTERIZE -> { + run { + // let the value between 1 and 256 + value = value * 19.0f + 1.0f + filter.setProperty("colorLevels", value.toInt()) + } + } + FILTER_PIXELLATION -> { + run { filter.setProperty("pixelSize", value) } + } + FILTER_SATURATION -> { + run { + // let the value between 0 - 2 + value *= 2.0f + filter.setProperty("saturation", value) + } + } + FILTER_CONTRAST -> { + run { + value *= 4.0f + filter.setProperty("contrast", value) + } + } + FILTER_EXPOSURE -> { + run { + // let the value between -10 and 10 + value = (value - 0.5f) * 20.0f + filter.setProperty("exposure", value) + } + } + FILTER_RGB -> { + run { + value *= 2.0f + filter.setProperty("greenAdjustment", value) + } + } + FILTER_HUE -> { + run { + value *= 360f + filter.setProperty("hueAdjustment", value) + } + } + FILTER_WHITE_BALANCE -> { + run { + value = value * 5000.0f + 2500.0f + filter.setProperty("temperature", value) + } + } + FILTER_LUMINANCE_RANGE -> { + run { filter.setProperty("rangeReductionFactor", value) } + } + FILTER_EMBOSS -> { + run { + value *= 4.0f + filter.setProperty("intensity", value) + } + } + FILTER_HALFTONE -> { + run { + value *= 0.05f + filter.setProperty("pixelSize", value) + } + } + FILTER_CROSSHATCH -> { + run { + value = 0.01f + value * 0.05f + filter.setProperty("crossHatchSpacing", value) + } + } + FILTER_SPHERE_REFRACTION -> { + run { filter.setProperty("radius", value) } + } + FILTER_GLASS_SPHERE -> { + run { filter.setProperty("radius", value) } + } + else -> { + } + } + } + + private fun createFilter(type: Int): GPUImageFilter { + val filters = getFilterList() + val filterClassName = filters.classNames[type] + return GPUImageFilter.create(filterClassName) + } + + private class FilterList { + var filters: MutableList = LinkedList() + var displayNames: MutableList = LinkedList() + var classNames: MutableList = LinkedList() + + fun addFilter(filter: Int, displayName: String, className: String) { + filters.add(filter) + displayNames.add(displayName) + classNames.add(className) + } + } + + interface OnFilterSelectedListener { + fun OnFilterSelected(newFilter: GPUImageFilter) + } +} diff --git a/app/src/main/java/com/cangwang/magic/util/RenderJNI.kt b/app/src/main/java/com/cangwang/magic/util/RenderJNI.kt index 5c22652..ee5c116 100644 --- a/app/src/main/java/com/cangwang/magic/util/RenderJNI.kt +++ b/app/src/main/java/com/cangwang/magic/util/RenderJNI.kt @@ -9,7 +9,7 @@ import javax.microedition.khronos.opengles.GL10 /** * Created by cangwang on 2018/10/12. */ -class RenderJNI(context: Context):GLSurfaceView.Renderer{ +class RenderJNI(context: Context) : GLSurfaceView.Renderer { companion object { init { System.loadLibrary("triangle-lib") @@ -18,23 +18,21 @@ class RenderJNI(context: Context):GLSurfaceView.Renderer{ external fun glesInit() external fun glesRender() - external fun glesResize(width:Int,height:Int) + external fun glesResize(width: Int, height: Int) external fun readShaderFile(assetManager: AssetManager) - val assetMaanager:AssetManager = context.assets - + val assetMaanager: AssetManager = context.assets override fun onDrawFrame(p0: GL10?) { glesRender() } override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) { - glesResize(width,height) + glesResize(width, height) } override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) { readShaderFile(assetMaanager) glesInit() } - } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallback.kt b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallback.kt index a49a671..6e1b464 100644 --- a/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallback.kt +++ b/app/src/main/java/com/cangwang/magic/view/CameraFilterSurfaceCallback.kt @@ -1,6 +1,5 @@ package com.cangwang.magic.view - import android.annotation.SuppressLint import android.graphics.SurfaceTexture import android.hardware.Camera @@ -23,12 +22,12 @@ import java.util.concurrent.Executors /** * Created by zjl on 2018/10/12. */ -class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ +class CameraFilterSurfaceCallback(camera: Camera?) : SurfaceHolder.Callback { private val mExecutor = Executors.newSingleThreadExecutor() - private val TAG= CameraFilterSurfaceCallback::class.java.simpleName!! - private var mSurfaceTexture:SurfaceTexture?=null - private var mCamera=camera + private val TAG = CameraFilterSurfaceCallback::class.java.simpleName!! + private var mSurfaceTexture: SurfaceTexture? = null + private var mCamera = camera private val mMatrix = FloatArray(16) private var width = 0 private var height = 0 @@ -37,7 +36,7 @@ class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width this.height = height - changeOpenGL(width,height) + changeOpenGL(width, height) } override fun surfaceDestroyed(holder: SurfaceHolder?) { @@ -50,11 +49,11 @@ class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ } } - fun initOpenGL(surface: Surface){ + fun initOpenGL(surface: Surface) { mExecutor.execute { - val textureId = OpenGLJniLib.magicFilterCreate(surface,BaseApplication.context.assets) + val textureId = OpenGLJniLib.magicFilterCreate(surface, BaseApplication.context.assets) // OpenGLJniLib.setFilterType(MagicFilterType.NONE.ordinal) - if (textureId < 0){ + if (textureId < 0) { Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } @@ -63,14 +62,14 @@ class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ try { mCamera?.setPreviewTexture(mSurfaceTexture) doStartPreview() - }catch (e:IOException){ - Log.e(TAG,e.localizedMessage) + } catch (e: IOException) { + Log.e(TAG, e.localizedMessage) releaseOpenGL() } } } - fun changeCamera(camera:Camera? ){ + fun changeCamera(camera: Camera?) { mExecutor.execute { mCamera = camera try { @@ -83,50 +82,50 @@ class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ } } - fun changeOpenGL(width:Int,height:Int){ + fun changeOpenGL(width: Int, height: Int) { mExecutor.execute { - OpenGLJniLib.magicFilterChange(width,height) + OpenGLJniLib.magicFilterChange(width, height) } } - fun drawOpenGL(){ + fun drawOpenGL() { mExecutor.execute { mSurfaceTexture?.updateTexImage() mSurfaceTexture?.getTransformMatrix(mMatrix) - if (isTakePhoto){ - val photoAddress = if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + if (isTakePhoto) { + val photoAddress = if (Build.BRAND == "Xiaomi") { // 小米手机 + Environment.getExternalStorageDirectory().path + "/DCIM/Camera/" + System.currentTimeMillis() + ".png" + } else { // Meizu 、Oppo + Environment.getExternalStorageDirectory().path + "/DCIM/" + System.currentTimeMillis() + ".png" } - OpenGLJniLib.magicFilterDraw(mMatrix,photoAddress) - }else { - OpenGLJniLib.magicFilterDraw(mMatrix,"") + OpenGLJniLib.magicFilterDraw(mMatrix, photoAddress) + } else { + OpenGLJniLib.magicFilterDraw(mMatrix, "") } } } - fun releaseOpenGL(){ + fun releaseOpenGL() { mExecutor.execute { OpenGLJniLib.magicFilterRelease() mSurfaceTexture?.release() - mSurfaceTexture=null - mCamera =null + mSurfaceTexture = null + mCamera = null } } - fun setFilterType(type:Int){ + fun setFilterType(type: Int) { mExecutor.execute { OpenGLJniLib.setFilterType(type) } } - fun doStartPreview(){ + fun doStartPreview() { mCamera?.startPreview() - cameraFocus(width/2.0f,height/2.0f) + cameraFocus(width / 2.0f, height / 2.0f) } - fun cameraFocus(x:Float,y:Float){ + fun cameraFocus(x: Float, y: Float) { mCamera?.let { it.cancelAutoFocus() CameraHelper.setFocusMode(it, Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) @@ -134,11 +133,11 @@ class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ } @SuppressLint("CheckResult") - fun takePhoto(){ - val rootAddress = if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + fun takePhoto() { + val rootAddress = if (Build.BRAND == "Xiaomi") { // 小米手机 + Environment.getExternalStorageDirectory().path + "/DCIM/Camera/" + System.currentTimeMillis() + ".png" + } else { // Meizu 、Oppo + Environment.getExternalStorageDirectory().path + "/DCIM/" + System.currentTimeMillis() + ".png" } // mCamera?.stopPreview() Observable.create(ObservableOnSubscribe { @@ -146,15 +145,17 @@ class CameraFilterSurfaceCallback(camera:Camera?):SurfaceHolder.Callback{ }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ -// mCamera?.startPreview() - if (!it){ - Toast.makeText(BaseApplication.context,"save fail",Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(BaseApplication.context,"save success",Toast.LENGTH_SHORT).show() + // mCamera?.startPreview() + if (!it) { + Toast.makeText(BaseApplication.context, "save fail", Toast.LENGTH_SHORT) + .show() + } else { + Toast.makeText(BaseApplication.context, "save success", Toast.LENGTH_SHORT) + .show() } - },{ -// mCamera?.startPreview() - Log.e(TAG,it.toString()) + }, { + // mCamera?.startPreview() + Log.e(TAG, it.toString()) }) } } \ No newline at end of file diff --git a/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt b/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt index 769deef..882b9ad 100644 --- a/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt +++ b/app/src/main/java/com/cangwang/magic/view/ImageFilterSurfaceCallback.kt @@ -21,17 +21,17 @@ import java.util.concurrent.Executors /** * Created by zjl on 2018/10/12. */ -class ImageFilterSurfaceCallback(path:String,filterType:Int):SurfaceHolder.Callback{ +class ImageFilterSurfaceCallback(path: String, filterType: Int) : SurfaceHolder.Callback { private val mExecutor = Executors.newSingleThreadExecutor() - private val TAG= ImageFilterSurfaceCallback::class.java.simpleName!! - private var mSurfaceTexture:SurfaceTexture?=null + private val TAG = ImageFilterSurfaceCallback::class.java.simpleName!! + private var mSurfaceTexture: SurfaceTexture? = null private val mMatrix = FloatArray(16) private var width = 0 private var height = 0 private var isTakePhoto = false private val imagePath = path - private var filterType=0 + private var filterType = 0 init { this.filterType = filterType @@ -40,7 +40,7 @@ class ImageFilterSurfaceCallback(path:String,filterType:Int):SurfaceHolder.Callb override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { this.width = width this.height = height - changeOpenGL(width,height) + changeOpenGL(width, height) setFilterType(filterType) } @@ -54,10 +54,12 @@ class ImageFilterSurfaceCallback(path:String,filterType:Int):SurfaceHolder.Callb } } - private fun initOpenGL(surface: Surface){ + private fun initOpenGL(surface: Surface) { mExecutor.execute { - val textureId = OpenGLJniLib.magicImageFilterCreate(surface,BaseApplication.context.assets,imagePath,ExifUtil.getExifOrientation(imagePath)) - if (textureId < 0){ + val textureId = + OpenGLJniLib.magicImageFilterCreate(surface, BaseApplication.context.assets, + imagePath, ExifUtil.getExifOrientation(imagePath)) + if (textureId < 0) { Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } @@ -69,65 +71,66 @@ class ImageFilterSurfaceCallback(path:String,filterType:Int):SurfaceHolder.Callb } } - - fun changeOpenGL(width:Int,height:Int){ + fun changeOpenGL(width: Int, height: Int) { mExecutor.execute { - OpenGLJniLib.magicImageFilterChange(width,height) - OpenGLJniLib.magicImageFilterDraw(mMatrix,"") + OpenGLJniLib.magicImageFilterChange(width, height) + OpenGLJniLib.magicImageFilterDraw(mMatrix, "") } } - fun drawOpenGL(){ + fun drawOpenGL() { mExecutor.execute { mSurfaceTexture?.updateTexImage() // mSurfaceTexture?.getTransformMatrix(mMatrix) - if (isTakePhoto){ - val photoAddress = if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + if (isTakePhoto) { + val photoAddress = if (Build.BRAND == "Xiaomi") { // 小米手机 + Environment.getExternalStorageDirectory().path + "/DCIM/Camera/" + System.currentTimeMillis() + ".png" + } else { // Meizu 、Oppo + Environment.getExternalStorageDirectory().path + "/DCIM/" + System.currentTimeMillis() + ".png" } - OpenGLJniLib.magicImageFilterDraw(mMatrix,photoAddress) - }else { - OpenGLJniLib.magicImageFilterDraw(mMatrix,"") + OpenGLJniLib.magicImageFilterDraw(mMatrix, photoAddress) + } else { + OpenGLJniLib.magicImageFilterDraw(mMatrix, "") } } } - fun releaseOpenGL(){ + fun releaseOpenGL() { mExecutor.execute { OpenGLJniLib.magicImageFilterRelease() mSurfaceTexture?.release() - mSurfaceTexture=null + mSurfaceTexture = null } } - fun setFilterType(type:Int){ + fun setFilterType(type: Int) { mExecutor.execute { OpenGLJniLib.setImageFilterType(type) - OpenGLJniLib.magicImageFilterDraw(mMatrix,"") + OpenGLJniLib.magicImageFilterDraw(mMatrix, "") } } @SuppressLint("CheckResult") - fun saveImage(){ - val rootAddress = if(Build.BRAND == "Xiaomi"){ // 小米手机 - Environment.getExternalStorageDirectory().path +"/DCIM/Camera/"+System.currentTimeMillis()+".png" - }else{ // Meizu 、Oppo - Environment.getExternalStorageDirectory().path +"/DCIM/"+System.currentTimeMillis()+".png" + fun saveImage() { + val rootAddress = if (Build.BRAND == "Xiaomi") { // 小米手机 + Environment.getExternalStorageDirectory().path + "/DCIM/Camera/" + System.currentTimeMillis() + ".png" + } else { // Meizu 、Oppo + Environment.getExternalStorageDirectory().path + "/DCIM/" + System.currentTimeMillis() + ".png" } Observable.create(ObservableOnSubscribe { it.onNext(OpenGLJniLib.saveImage(rootAddress)) }).subscribeOn(Schedulers.from(mExecutor)) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ - if (!it){ - Toast.makeText(BaseApplication.context,"save fail",Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(BaseApplication.context,"save success",Toast.LENGTH_SHORT).show() + if (!it) { + Toast.makeText(BaseApplication.context, "save fail", Toast.LENGTH_SHORT) + .show() + } else { + Toast.makeText(BaseApplication.context, "save success", Toast.LENGTH_SHORT) + .show() } - },{ - Log.e(TAG,it.toString()) + }, { + Log.e(TAG, it.toString()) }) } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_camera_sample.xml b/app/src/main/res/layout/activity_camera_sample.xml new file mode 100755 index 0000000..b3565b6 --- /dev/null +++ b/app/src/main/res/layout/activity_camera_sample.xml @@ -0,0 +1,54 @@ + + + + + + + +