gradle에서 필요한거 추가해야함.
buld.gradle(app) -> implementation 'gun0912.ted:tedpermission:2.0.0' 을 추가해준다.
sync now눌러주기
Surface View란?
카메라 어플을 생각해보자. 카메라 어플을 키면 실시간으로 미리보기 화면이 동작한다. 이는 카메라 앱 화면의 뷰가 엄청 짧은 시간내에 아주 많은 프레임을 계속해서 그리기 때문인데, 엄청난 자원이 필요하다. 이런 경우 화면을 그리는데에만 많은 자원이 필요한데, 어플에서 다른 것들을 처리하느라 화면이 버벅거리면서 업데이트가 늦어질 수가 있다. 이와 같은 경우에 사용하는 것이 그래픽 그리기가 빠른 서피스뷰이다.
SurfaceView는 View를 상속받는 클래스이다.
일반 View는 onDraw 메소드를 시스템에서 자동으로 호출해줌으로써 화면을 그린다. 그래서 화면에 늦게 그려질 수도 있다.
SurfaceView는 그리기를 시스템에 맡기는 것이 아니라 스레드를 이용해 강제로 화면에 그림으로써 원하는 시점에 바로 화면에 그릴 수 있다.
그래서 SurfaceView는 애니메이션이나 동영상과 같이 연산처리가 많이 필요한 뷰를 위해 사용된다.
SurfaceView는 더블 버퍼링 기법을 이용하여 SurfaceHolder가 Surface에 미리 그리고 이 Surface가 SurfaceView에 반영되는 방식이다.
SurfaceView는 자기 영역 부분의 Window를 뚫어서(punch) 자신이 보여지게끔 하고 Window와 View가 블렌딩되어 화면에 보여지게 된다.
SurfaceView로부터 상속받을 경우 디폴트로 구현해야 할 메소드가 있다.
- public void onDraw (Canvas canvas) : 화면을 그린다.
- public void surfaceChanged() : 뷰가 변경될 때 호출된다.
- public void surfaceCreated() : 뷰가 생성될 때 호출된다.
- public void surfaceDestroyed() : 뷰가 종료될 때 호출된다.
2. 서피스를 제어하는 서피스 홀더
사실 안드로이드 운영체제의 정책상 메인 스레드이외의 쓰레드는 캔버스(사실 캔버스 뿐만이 아니라 앱의 UI 구성요소)에 직접 접근이 허락되지 않는다. 이는 하나의 프로세스 이내의 여러개의 스레드는 공통의 메모리 자원을 공유하므로 여러 스레드가 같은 자원에 동시에 접근했을 때 일어나는 데드락(Dead Lock)을 방지하기 위함이다. 따라서 서피스뷰를 그리기 위해서는 다른 방법을 써야하는데, 서피스홀더(Surface Holder)라는 것을 사용한다. 서피스뷰는 뷰가 그려지는 버퍼일 뿐이다. 직접적으로 그림을 그리고 서피스뷰의 상태를 제어하는 것은 서피스뷰의 서피스홀더(Surface Holder)이다. 아래 그림은 이러한 서피스뷰와 서피스홀더의 관계를 보여준다.
package com.example.videorecordexample;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
}
출처: https://m.blog.naver.com/muri1004/221054311714
https://dogrushdev.tistory.com/255
오류 해결 (진짜 미칠뻔)
이게 없다고 하길래 추가해줌
근데
버전이 android버전이라서
gradle버전을 android X로도 바꿔봤음
해결안됨
그래서 최근버전인 androidx로 손수바꿔줌
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
duplicate class 라고, 자꾸 클래스가 중복되었다는 오류가 뜨길래
에 들어가서 해결해줌
gradle.properties라는 파일에 들어가서
android.useAndroidX=true
android.enableJetifier=true
이걸 추가해주면 되는거임
엥 근데 또 오류났음
그래서 엔터를 쳐봤음 저거 androidx 위에 공간을..줬떠니 해결됨
main.js
package com.example.videorecordexample;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
//hardware카메라로 임포트
private Camera camera;
private MediaRecorder mediaRecorder;
private Button btn_record;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
//현재녹화중이냐? 아직은 false
private boolean recording = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TedPermission.with(this)
.setPermissionListener(permission)
//팝업메시지띄우기
.setRationaleMessage("녹화를 위하여 권한을 허용해주세요")
.setDeniedMessage("권한이 거부되었습니다. 설정 > 권한에서 허용해주세요.")
//menifest에서 설정한것들 권한설정
.setPermissions(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO)
.check();
btn_record = (Button)findViewById(R.id.btn_record);
btn_record.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recording) {
//녹화를 하고있는 상황이면 다시한번눌렀을때 녹화 끝내기 구문임 버튼 하나라서
mediaRecorder.stop();
mediaRecorder.release();
camera.lock();
recording = false;
}else{
// 별도의 ui처리를 스레드안에서 해주는게 과부하가 덜 됨.
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"녹화가 시작되었습니다.",Toast.LENGTH_SHORT).show();
try {
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
//화질 좋게해주는
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_720P));
mediaRecorder.setOrientationHint(90); //촬영각도
mediaRecorder.setOutputFile("/sdcard/test.mp4");
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface()); //미리보기, 동영상녹화시 보이는화면
mediaRecorder.prepare();
mediaRecorder.start();
recording = true;
}catch(Exception e){
e.printStackTrace();
//예외발생시 동영상 녹화 끄기기
mediaRecorder.release();
}
}
});
}
}
});
}
PermissionListener permission = new PermissionListener() {
//permission허용되었을때
@Override
public void onPermissionGranted() {
Toast.makeText(MainActivity.this,"권한허가",Toast.LENGTH_SHORT).show();
camera = Camera.open();
camera.setDisplayOrientation(90);
surfaceView = (SurfaceView)findViewById(R.id.surfaceView);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(MainActivity.this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
//permission거부되었을때
@Override
public void onPermissionDenied(List<String> deniedPermissions) {
Toast.makeText(MainActivity.this,"권한거부",Toast.LENGTH_SHORT).show();
}
};
//surface view선언했을때 생명주기
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
//surface view에 변화일어나고있을때 감지해서 바뀔때마다 호출되는 생명주기, 그때마다 카메라 초기화해줌
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
refreshCamera(camera);
}
//카메라를 초기화 해주는 작업
private void refreshCamera(Camera camera) {
if (surfaceHolder.getSurface() == null){
return;
}
try {
camera.stopPreview();
}catch(Exception e){
e.printStackTrace();
}
serCamera(camera);
}
private void serCamera(Camera cam) {
camera = cam;
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
}
activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="녹화 시작~!"
/>
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
gradle(app)
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.videorecordexample"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'gun0912.ted:tedpermission:2.2.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
gradle.properties
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.enableJetifier=true
manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.videorecordexample">
<uses-permission android:name="android.permission.CAMERA" />
<!-- 외부저장소 권한-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.VideoRecordExample">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
문제점 : 화면녹화하는 장면이안보임, 미리보기 코드에 문제가있는듯함.
녹화가 안돼애애액 소리만들리고
'✍2021,2022 > app(android studio)' 카테고리의 다른 글
앱만들기.19 (spinner드롭다운) (0) | 2021.07.20 |
---|---|
앱만들기.18(FCM푸시알림) (0) | 2021.07.19 |
앱만들기 .16(Service 백그라운드 음악) (0) | 2021.07.19 |
앱만들기. 15(Dialog) (0) | 2021.07.17 |
앱만들기. 14(Thread & Handler ) (0) | 2021.07.17 |