오늘은 Android 에서 User Data를 저장하는 방법에 대해 이야기 해보도록 하겠습니다.

사용자가 가장 많이 접할 수 있는 데이터 저장 방법은 Internal Storage 와 External Storage 를 이용하는 것입니다.

External Storage 라고 해서 SDCard 같은 외부 저장장치를 연결 해야만 생기는 것이라고 오해할 수 있지만 개념은 살짝 다릅니다.

Android 에서 이야기하는 Internal Storage 는 애플리케이션 데이터가 저장되는 영역을 말하고, 사진이나 동영상 등이 저장될 수 있는 사용자 영역을 External Storage 라고 이야기 합니다.

Internal/External Storage 외에도 데이터를 저장하는 방법은 여러가지가 있겠지만, 이번 포스팅에서는 그 중 가장 간단한 방법인 Internal Storage 와 External Storage 를 사용하는 방법에 대해 알아 보겠습니다.

 

1. Internal Storage

Internal Storage 는 External Storage 와는 다르게 별도의 Permission 추가 없이 사용할 수 있는 저장장치 입니다.  

Internal Storage 에 저장된 파일은 자신의 앱에서만 액세스 가능하며, 사용자가 앱을 삭제할 경우 시스템이 Internal Storage 에서 앱의 모든 파일을 제거하게 됩니다.

=> 즉, 사용자와 다른 앱이 자신의 파일에 액세스 하는것을 원하지 않을 경우 가장 적합하게 사용될 수 있습니다.

Internal Storage 에 데이터를 저장할 때에는 openFileOutput() 함수를 사용 합니다.

이 함수는 내부 디렉터리의 파일에 데이터를 쓰는 FileOutputStream 을 retun 하여, 내부 저장소에 파일을 쓸 수 있도록 합니다.

 

2. External Storage

External Storage 를 사용하기 위해서는 Internal Stroage 와는 다르게 파일을 읽고 쓰기 위한 Permission 이 필요합니다.

    <USES-PERMISSION android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <USES-PERMISSION android:name="android.permission.READ_EXTERNAL_STORAGE" />

외부 저장소는 항상 사용 가능한 것은 아니고, 사용자가 sdcard 등의 외부 저장소를 mount 했을 경우에만 사용 가능 합니다.

그렇기 때문에 외부저장소를 사용하기 전에, getExternalStorageState() 함수를 호출하여 외부 저장소가 사용 가능한지에 대해 확인하는 것이 좋습니다.

이 함수가 return 하는 값이 MEDIA_MOUNTED 일 경우에, External Stroage 에 read/write 가 가능한 상태입니다.

외부저장소는 내부저장소와는 다르게 모든 앱에서 읽을 수 있기 때문에, 다른 앱과 공유하기 원하는 파일들을 저장하기 적합합니다.

 

그러면 이제부터 Internal Storage 와 External Strorage 에 파일을 저장하는 예제를 다뤄보도록 하겠습니다.

1. Layout 구성

Layout은 EditText, Button 3개, TextView 를 두어, EditText 에 입력된 String 을 Internal 혹은 External Storage 에 저장하고 TextView 에 출력할수 있도록 구성하겠습니다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.codetravel.storeuserdata.MainActivity">

<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter your message"/>

<Button
android:id="@+id/bt_internal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Internal"/>


<Button
android:id="@+id/bt_external"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="External"/>

<TextView
android:id="@+id/tv_output"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

<Button
android:id="@+id/bt_print"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Print message" />
</LinearLayout>

 

2. Internal Stroage 에 저장하기

위에서 언급했다시피, Internal Storage 를 사용하기 위해서는 openFileOutput(..) 을 이용합니다.

이 함수는 자신의 앱에서만 사용할 수 있는 private 한 파일을 open 합니다.

첫번째 parameter 로 open 할 파일의 이름을 전달 해주고, 두번째 parameter 로 operation mode 를 전달해 줍니다.

file open mode 에는 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITEBLE, MODE_APPEND 4가지가 있습니다.

MODE_PRIVATE 는 default mode 로 파일을 생성한 어플리케이션에서만 이 파일에 접근 할 수 있게 하는 모드입니다.

MODE_APPEND 도 MODE_PRIVATE 와 비슷하지만, 파일이 이미 있을 경우에 내용을 그 뒤에 이어 붙이게 됩니다.

MODE_WORLD_READBLE 과 MODE_WORLD_WRITEBLE 은 보안 문제로 API level 17에서 부터 지워진 모드라고 하네요. ^^

 

이 예제에서는 MODE_PRIVATE 를 이용해 파일을 생성해 보았습니다.

View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View view) { String inputData = mEtInput.getText().toString(); switch(view.getId()) { case R.id.bt_internal: FileOutputStream fos = null; try { fos = openFileOutput("internal.txt", Context.MODE_PRIVATE); fos.write(inputData.getBytes()); fos.close();; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } break;

 

Button Click Listener 를 하나 생성 한 후, Internal 버튼이 눌렸을 경우 Internal Storage 에 저장하기 위해서 openFileOuput 함수를 호출해 FileOutputStream 을 가져왔습니다.

이 FileOutputStream 을 이용해 EditText 로 부터 입력받은 String 을 파일에 write 해 해줍니다.

openFileOutput 을 통해 생성된 FileOutputStream 은 /data/data/[project명]/ 아래에 파일을 저장하게 됩니다.

 

3. External Storage 에 저장하기

external 버튼이 눌렸을 경우에는 먼저 getExternalStorageState() 함수를 통해 외부저장장치가 Mount 되어 있는지를 확인 합니다.

Mounted 되어 있는 경우에만 File 을 하나 생성하고, FileWriter 를 이용해 EditText 의 내용을 저장해 주었습니다.

getExternalStorageDirectory() 를 통해 가져온 경로는 /storage/external/0/ 의 위치에 파일을 저장하게 됩니다.

    case R.id.bt_external:
         if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
             File file = new File(Environment.getExternalStorageDirectory(), "External.txt");
             try {
                 FileWriter fw = new FileWriter(file, false);
                 fw.write(inputData);
                 fw.close();
             } catch(IOException e) {
                 e.printStackTrace();
             }
         }
         else {
             Log.d(TAG, "External Storage is not ready");
         }

 

 

4. 데이터 출력하기

위에서 저장한 데이터를 출력하는 코드입니다.

출력 버튼이 눌리면, StringBuffer 를 하나 생성해 줍니다. 여기에 쓰여지는 모든 데이터를 TextView 에 뿌려주게 될 것입니다.

Internal Storage 의 데이터를 읽어올 땐 openFileInput 함수를 이용해서 FileInputStream 을 가지고 오고, ExternalStorage 의 파일을 읽어올 때는 FileReader 를 이용해 읽어오면 됩니다.

case R.id.bt_print:
     StringBuffer buffer = new StringBuffer();
     String data = null;
     FileInputStream fis = null;
     try {
         fis = openFileInput("internal.txt");
         BufferedReader iReader = new BufferedReader(new InputStreamReader((fis)));

         data = iReader.readLine();
         while(data != null)
         {
             buffer.append(data);
             data = iReader.readLine();
         }
         buffer.append("\n");
         iReader.close();
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }

     String path = Environment.getExternalStorageDirectory() + "/External.txt";

     try {
         BufferedReader eReader = new BufferedReader(new FileReader(path));
         data = eReader.readLine();
         while(data != null)
         {
             buffer.append(data);
             data = eReader.readLine();
         }
         mTvOutput.setText(buffer.toString()+"\n");
         eReader.close();

     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }

     break;

 

아래와 같이 간단하게 완성이 되었습니다. 각 버튼을 눌렀을 때 Internal/External Storage 에 저장하고, Print 버튼을 눌렀을 때 저장된 내용이 출력되도록 한 예제입니다. 작성된 코드는 https://github.com/bettercho/MyGithub/tree/master/storeuserdata 를 참고하세요.

 

 

 

 

 

Kotlin에서는 Range expression 을 통해서 개발자들이 편하게 사용할 수 있도록 해줍니다.

 

가장 흔하게 if 문이나 for문에서 사용될 수 있습니다.

".." operator 를 갖는 rangeTo 함수로 표현합니다.

아래는 C / JAVA 언어의 if (i>=1 && i<= 10) 조건문과 같은 의미를 가집니다.

if (i in 1..10) { // equivalent of 1 <= i && i <= 10
    println(i)
}

아래와 같이 반복문에서도 사용합니다

이때 1..4 와 같이 커지는 순서로 for문을 동작 시키게 되는데요

아래와 같이 4..1 이 되면 아무것도 수행되지 않게 됩니다.

for (i in 1..4) print(i) // prints "1234"

for (i in 4..1) print(i) // prints nothing

그렇다면 C언어에서처럼 i값을 점점 작아지게 하고 싶으면 어떻게 하면 될까요?

C언어에서 for (int i = 4 ; i > 0 ; i --) 와 같이 사용되는 반복문을

Kotlin에서는 downTo() 함수를 통해 아래와 같이 표현할 수 있습니다.

for (i in 4 downTo 1) print(i) // prints "4321"

 

또한 step을 사용하여 일정한 간격으로 i를 증가시킬 수도 있고,

downTo와 step을 함께 사용하여 역순으로 일정간격으로 i를 감소시킬 수도 있습니다.

첫번째 for문은 1부터 4까지 2씩 건너뛰므로, 1과 3이 출력됩니다.

두번째 for문은 4부터 1까지 2씩 건너뛰므로, 4와 2가 출력됩니다.

for (i in 1..4 step 2) print(i) // prints "13"

for (i in 4 downTo 1 step 2) print(i) // prints "42"

 

until 함수를 사용하여 범위를 결정할 수도 있습니다.

(아래의 식에서 i는 1에서 9까지만 수행합니다. 10은 포함되지 않습니다.)

C언어에서보다는 쉽고 다양하게 표현할 수 있지만,

처음 접한다면 익숙한 C언어의 표현을 바꿔서 하기에 좀 불편하기도 할 것 같아요.

for (i in 1 until 10) { // i in [1, 10), 10 is excluded
     println(i)
}

 

마지막으로 last 함수를 사용하여 마지막 i 값을 확인할 수 있습니다.

(1..12 step 2).last == 11  // progression with values [1, 3, 5, 7, 9, 11]
(1..12 step 3).last == 10  // progression with values [1, 4, 7, 10]
(1..12 step 4).last == 9   // progression with values [1, 5, 9]

 

 

 

프로세스를 생성하고자 할 때 fork 함수를 사용하면 됩니다.

fork 함수를 호출하는 프로세스는 부모 프로세스가 되고 새롭게 생성되는 프로세스는 자식 프로세스가 됩니다.

fork 함수에 의해 생성된 자식 프로세스는 부모 프로세스의 메모리를 그대로 복사하여 가지게 됩니다.

그리고 fork 함수 호출 이후 코드부터 각자의 메모리를 사용하여 실행됩니다


fork 함수를 사용하기 위해서는 unistd.h를 include하면 됩니다.

fork 함수는 unistd.h 파일에 system call로 정의되어 있습니다.

// unistd.h header file

pid_t fork(void); // 성공 시 : 부모 프로세스에서는 자식 프로세스의 PID값을 반환 받음

// 자식 프로세스에서는 0 값을 반환 받음

// 실패 시 : 음수 값(-1) 반환


간단한 예제 코드를 살펴 보겠습니다.

#include <stdio.h>
#include <unistd.h>

int main() {
    int x;
    x = 0;
    
    fork();
    
    x = 1;
    printf("PID : %ld,  x : %d\n",getpid(), x);
    
    return 0;
}


실행 결과는 다음과 같습니다.

fork 함수 코드 이후부터는 부모 프로세스와 자식 프로세스가 각자의 x = 1, printf() 코드를 실행하였습니다.

그렇기 때문에 PID 43889(부모 프로세스)에서 x 값은 1을 출력하였고 PID 43895(자식 프로세스)에서도 x 값은 1이라고 출력하였습니다.

PID : 43889,  x : 1

PID : 43895,  x : 1 


그림을 통하여 다시 한번 보도록 하겠습니다.

fork 함수가 실행 된 직후에는 자식 프로세스 부모 프로레스와 동일한 주소 공간의 복사본을 가지게 됩니다.

fork 함수 실행 이후 , 부모와 자식 프로세스는 동일한 코드 (x = 1, printf())를 각자 메모리상에서 실행하고 있습니다.


이번에는 fork 함수의 리턴값을 검사하여 부모와 자식 코드를 각각 분리하도록 하겠습니다.

fork 함수는 부모 프로세스에게는 자식프로세스의 PID를 반환하며 자식 프로세스에게는 0을 반환합니다.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    
    pid_t pid;
    
    int x;
    x = 0;
    
    pid = fork();
    
    if(pid > 0) {  // 부모 코드
        x = 1;
        printf("부모 PID : %ld,  x : %d , pid : %d\n",(long)getpid(), x, pid);
    }
    else if(pid == 0){  // 자식 코드
        x = 2;
        printf("자식 PID : %ld,  x : %d\n",(long)getpid(), x);
    }
    else {  // fork 실패
        printf("fork Fail! \n");
        return -1;
    }
    
    return 0;

}


실행 결과는 다음과 같습니다. 

pid 반환값을 검사하여 부모 프로세스와 자식 프로세스에서 실행될 코드를 별도로 작성할 수 있는것을 확인 하였습니다.

부모 PID : 46834,  x : 1 , pid : 46838

자식 PID : 46838,  x : 2 


fork 함수가 실행 된 직후에는 자식 프로세스 부모 프로레스와 동일한 주소 공간의 복사본을 가지게 됩니다.

하지만 fork 함수 이후의 코드가 pid값을 기준으로 분리되어 있습니다.

따라서 각 프로세스의 메모리 공간의 x값은 서로 달라지게 됩니다. 


지금까지 fork 함수 사용에 대해서 살펴 보았습니다.

BeautifulSoup 은 HTML 및 XML 파일에서 원하는 데이터를 손쉽게 Parsing 할 수 있는 Python 라이브러리 입니다.

오늘은 Beautiful Soup 라이브러리를 활용하여 HTML 코드 안에서 원하는 Data 를 뽑아내는 예제를 다뤄보려고 합니다.

 

1. Beautiful Soup 설치하기

Beautiful Soup 라이브러리는 Python 에서 기본적으로 제공하는 라이브러리에 해당하지 않기 때문에 별도의 설치가 필요합니다.

Windows 에서는 cmd 창에서 간단하게 pip 명령으로 설치할 수 있습니다.

- pip install beautifulsoup4


pip 는 Python 설치 디렉토리의 Scripts 폴더 밑에 있기 때문에, 해당 위치로 이동하여 명령어를 실행해 주어야 합니다.

설치가 완료되면 Python 설치 디렉토리 밑의 Lib/site-packages/ 위치에 bs4 디렉토리가 생성된 것을 확인 할 수 있습니다.

 

2. HTML 에서 원하는 정보 추출하기 예제

먼저 BeautifulSoup 라이브러리 사용을 위해 bs4 를 import 합니다.

from bs4 import BeautifulSoup

 예제에 사용될 HTML 문서는 다음과 같습니다.

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

문서를 Parsing 하기 위해서 BeautifulSoup 생성자에 해당 문서를 인자로 전달 해주어야 합니다.

아래와 같이 문자 열을 전달할 수도 있고, file handle 을 전달 할 수도 있습니다.

from bs4 import BeautifulSoup

with open("index.html") as fp:
    soup = BeautifulSoup(fp)

soup = BeautifulSoup("<html>data</html>")

위의 html_doc 문자열을 전달하여 BeautifulSoup 객체를 생성 합니다.

그렇게 생성한 soup 객체를 통해 HTML Parsing 을 위해 제공되는 BeautifulSoup 라이브러리의 여러가지 API 들을 사용해 보겠습니다.

if __name__  == "__main__":
    soup = BeautifulSoup(html_doc, 'html.parser')
    print('1. ', soup.title)
    print('2. ', soup.title.name)
    print('3. ', soup.title.string)
    print('4. ', soup.title.parent.name)
    print('5. ', soup.p)
    print('6. ', soup.p['class'])
    print('7. ', soup.a)
    print('8. ', soup.find_all('a'))
    print('9. ', soup.find(id="link3"))

 

1번 부터 9번까지 파싱 결과를 살펴보겠습니다.

원하는 tag 를 가지고 오고 싶을 때 1번과 5번 경우처럼 간단하게 soup.(원하는tag 명) 을 사용할 수 있습니다.

특정 tag 로 감싸진 내용만 가져오고 싶다면 3번과 같이 soup.title.string 을 사용하면 됩니다.

.(tag 명)의 경우 해당 태그를 포함하여 그 태그가 끝날 까지의 문장을 가지고 오고, .name, .string, .parent.name 등을 통해 더 자세한 정보들을 얻어 올 수 있습니다.

6번의 경우 soup.p['class'] 라고 하면 tag 가 p 인 것들 중 속성이 class 인 부분을 파싱 합니다.

7번과 8번의 다른점은 soup.a 의 경우 html 중 tag 가 a 인 첫번째 항목을 뽑아내지만,  find_all 을 이용하면 tag 가 a 인 것들을 모두 리스트의 형태로 뽑아 낼 수 있습니다.

 

3. 네이버 영화 랭킹 페이지에서 영화 목록 가져오기

이번에는 자주 들어가는 Naver 포탈 사이트에서 제공하는 영화 목록을 가져와 보도록 하겠습니다.

먼저 Parsing 할 주소는 http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20180117 입니다.

페이지에 들어가 보면 평점이 가장 높은 영화부터 쭉 리스팅이 되어있습니다.  많은 정보들 중에 영화의 제목만 파싱해 오도록 하겠습니다.

 

먼저 BeautifulSoup 을 하나 생성하고, 인자로 네이버 영화 랭킹 페이지를 전달 합니다.

soup = BeautifulSoup(urllib.request.urlopen('http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20161120').read(), 'html.parser')

 

해당 페이지가 어떤 tag 들로 어떻게 구성되어있는지 보고싶다면,

print(soup.prettify())

를 이용할 수도 있지만, Chrome 에서 제공하는 개발자도구를 이용하면 더 편리합니다. (도구더보기 -> 개발자도구)

html 코드로 보면 아래와 같이 영화 제목은 'div' tag 의 'tit5'를 속성으로 가진 항목들에 포함되어 있습니다.

<div class="tit5"> <a href="/movie/bi/mi/basic.nhn?code=17421" title="쇼생크 탈출"> 쇼생크 탈출 </a> </div>

그래서 생성한 BeautifulSoup 객체의 find_all 함수를 이용해서 영화 제목이 담긴 div tag 를 가진 항목을 list 로 뽑아 내었습니다.

if __name__  == "__main__":
    soup = BeautifulSoup(urllib.request.urlopen('http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20161120').read(), 'html.parser')
    res = soup.find_all('div', 'tit5')
    print(res)

 

위 코드에서 res 를 출력해보면, 의도했던대로 모든 div tag 항목이 저장되어 있는 것을 확인 할 수 있습니다.

위와 같이 1차적으로 Parsing 이 되었고, 완벽하게 영화 제목만 가져오기 위해 다시한번 파싱을 해줍니다.

동일 Tag 로 잘 분리가 되어있다면, get_text() 를 이용해 해당 tag 에서 text 정보면 가져올 수 있습니다.

그래서 최종적으로 완성된 코드는 아래와 같이 간단합니다.

if __name__  == "__main__":
    soup = BeautifulSoup(urllib.request.urlopen('http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20161120').read(), 'html.parser')
    res = soup.find_all('div', 'tit5')

    for n in res:
        print(n.get_text())

결과를 출력해 보면 원하는 데로 제목만 잘 출력이 되었습니다. ^^

결과적으로, 네이버 페이지에 있는 많은 영화 제목들을 단 몇줄로 간단하게 출력해본 예제입니다.

예제는 네이버 페이지이지만, 다양한 사이트에서 원하는 정보를 뽑아낼 때 활용할 수 있겠습니다.

 

 

지난 포스팅에서 'MediaRecorder 를 이용한 오디오 레코딩 예제' 를 살펴보았습니다.

이번에는 지난번에 생성한 Project에 MediaRecorder API 를 이용한 캠코딩 예제를 추가해 보도록 하겠습니다.

즉 지난번에는 음성만 녹음하였다면, 이번에는 카메라로 들어오는 화면도 함께 캠코딩하는 것입니다.

 

1. Permission

우선 캠코딩을 하기 위해서 Camera 를 사용해야 하기 때문에, AndroidManifest.xml 에 Camera 관련 Permission 을 추가해 줍니다.

총 3개의 Permission 입니다. 음성을 녹음할 수 있는 권한과, 캠코딩된 파일을 저장해야 하기 때문에 External Storage 에 쓸수 있는 권한, 그리고 카메라로 들어오는 영상을 캠코딩해야 하므로 Camera 사용 권한도 함께 줄 수 있도록 합니다.

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>

 

2. Layout 구성하기

Layout은 지난번 구현하였던 오디오 레코딩 예제에 버튼을 하나 추가해서 캠코딩을 시작하고 정지할 수 있게 합니다.

카메라 프리뷰 화면과 녹화된 영상을 재생할 때 Video 를 뿌려줄 SurfaceView 를 하나 구성합니다.  

<Button
android:id="@+id/bt_camcording"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Camcording"/>



<SurfaceView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

캠코딩 시작 버튼이 눌렸을 때 캠코더를 시작하고, 다시 눌렸을 때 종료할 수 있도록 아래와 같이 OnClickListener 를 구현 해 줍니다.

mBtCamcording = (Button)findViewById(R.id.bt_camcording);
mBtCamcording.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
hasVideo = true;
initVideoRecorder();
startVideoRecorder();
}
});

 

3. Camera 와 Surface 연결 및 레코딩 시작 하기

위에서 버튼이 눌리면 가장 먼저 실행되는 initVideoRecorder() 함수에서는 Camera Device 를 open 하고, SurfaceHolder 를 초기화 하는 일을 수행 합니다.

Camera.open() 함수는 카메라 인스턴스를 리턴하고, 사용자는 이 객체를 통해 카메라 Device 에 접근할 수 있습니다.

Camera.open(int) 함수를 이용하면 Device 에 장착되어 있는 여러개의 카메라 중, 원하는 카메라에 접근하여 사용할 수 있습니다.

void initVideoRecorder() {
mCamera = Camera.open();
mCamera.setDisplayOrientation(90);
mSurfaceHolder = mSurface.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

mSurfaceHolder 에는 SurfaceView 의 getHolder 메소드를 통해 SurfaceHolder 의 인스턴스를 연결해 주고, Surface 의 변화가 있을 때 처리를 위해 Callback 을 등록해 줍니다.

두번째로 불리는 startVideoRecorder 함수에서는 MediaRecorder 를 초기화 하고, 실제 레코딩을 수행 합니다.

void startVideoRecorder() {
if(isRecording) {
mRecorder.stop();
mRecorder.release();
mRecorder = null;

mCamera.lock();
isRecording = false;

mBtCamcording.setText("Start Camcording");
}
else {
runOnUiThread(new Runnable() {
@Override
public void run() {
mRecorder = new MediaRecorder();
mCamera.unlock();
mRecorder.setCamera(mCamera);
mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mRecorder.setOrientationHint(90);

mPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record.mp4";
Log.d(TAG, "file path is " + mPath);
mRecorder.setOutputFile(mPath);

mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
try {
mRecorder.prepare();
}catch(Exception e){
e.printStackTrace();
}
mRecorder.start();
isRecording = true;

mBtCamcording.setText("Stop Camcording");
}
});
}
}

음성 녹음만 할 때는 Audio 에 관련된 설정들만 해주면 되었지만, 캠코딩인 경우에는 Video Source 에 대한 설정도 해주어야 합니다.

setCamera(..) :  비디오 레코딩 시 사용할 카메라를 설정해 줍니다.

setVideoSource(..) : 레코딩 시 비디오 소스를 설정합니다. DEFAULT, CAMERA, SURFACE 세개의 값 중 하나를 선택하면 됩니다.

setVideoEncoder(..) : 비디오 코덱을 설정합니다. Video Encoder 의 경우 DEFAULT, H263, H264, MPEG_4_SP, VP8, HEVC 등을 제공하고 있습니다.

Android 에서 제공하는 Video/Audio Encoder/Decoder 의 정보는 하기 사이트에서 더 자세하게 볼 수 있습니다.

https://developer.android.com/guide/topics/media/media-formats.html

 

각종 설정들을 마무리 하고 mRecorder.start() 를 호출하면 드디어 캠코딩이 시작됩니다.

캠코딩 시 Preview 화면은 MediaRecorder 의 setPreviewDisplay(..) 함수를 통해 설정해 준 SurfaceView 에 뿌려지게 됩니다.

 

4. 레코딩 한 파일 재생하기

재생의 경우에는 Audio 파일 재생과 완전히 동일하지만, Video 의 경우 화면을 뿌려주기 위한 Surface View 만 추가로 지정해 주면 됩니다.

기존에 만들어 두었던 Player Button 의 OnClickListener 에 아래와 같이 Video 를 가질 경우, 만들어 놓았던 SurfaceHolder 를 지정 해 주고,

if(hasVideo == true) {
mPlayer.setDisplay(mSurfaceHolder);
mPlayer.setOnCompletionListener(mListener);
}

Video 재생이 끝났을 경우 Button 의 Text 를 변경해 주기 위한 OnCompletionListener 를 등록해 줍니다.

MediaPlayer.OnCompletionListener mListener = new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mBtPlay.setText("Start Playing");
}
};

등록된 Listener 에서는 간단하게 버튼의 Text 만 변경해 주었습니다.

 

여기까지 아직 많은 예외처리들이 필요하지만, 기본적인 오디오, 비디오 레코더를 완성 하였습니다.

MediaRecorder API와 Camera Class 에서 제공하는 API를 사용하면 이렇게 간단하게 레코딩 기능을 구현할 수 있습니다.

전체 소스는 https://github.com/bettercho/MyGithub/tree/master/MediaRecorder/app/src 를 참고해 주세요.

 

 

+ Recent posts