2015. 7. 23.

SD card에 있는 video file 을 Bitmap 으로 추출하기(MediaMetadataRetriever사용하기)

프로젝트 진행 도중에 sdcard에 저장되어있는 mp4 file 을 불러와서 
Bitmap으로 추출해야하는 작업이 필요했다.


소스를 분석하기에 앞서

MediaMetadataRetriever에 대해서 알 필요가 있다고 생각하여 간단히 설명하려한다.
말그대로 media의 metadata를 가져올수 있는 class이다.

google 공식 api에서는

MediaMetadataRetriever

extends Object
java.lang.Object
   ↳android.media.MediaMetadataRetriever

Class Overview


MediaMetadataRetriever class provides a unified interface for retrieving frame and meta data from an input media file.

MediaMetadataRetriever 클래스는 입력 된 미디어 파일로부터 프레임 메타 데이터를 검색하기위한 통일​​ 된 인터페이스를 제공한다.







라고 설명하고 있다. 이제소스를 보자


import android.app.Activity;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;


public class MainActivity extends Activity {
    private File    videoFile;
    private Uri     videoFileUri;
    private MediaMetadataRetriever retriever;
    private ArrayList<Bitmap> bitmapArrayList;
    private MediaPlayer mediaPlayer;
    private Bitmap bitmap;
    private Thread thread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // test.mp4 동영상파일을 불러온다.
        videoFile       = new File(Environment.getExternalStorageDirectory()+"/49.mp4");
        videoFileUri    = Uri.parse(videoFile.toString());
        // instance 생성
        retriever       = new MediaMetadataRetriever();
        // 추출할 bitmap 을 담을 array 생성
        bitmapArrayList = new ArrayList<Bitmap>();
        // 사용할 data source의 경로를 설정해준다.
        retriever.setDataSource(videoFile.toString());
        // video file의 총 재생시간을 얻어오기위함
        mediaPlayer = MediaPlayer.create(getBaseContext(), videoFileUri);
        // 나는 3초 짜리 동영상을 사용했다. millisecond = 3000
        int millisecond = mediaPlayer.getDuration();
        // 1000씩 증가를 시킨 이유는 총 3초짜리 동영상 을 1초마다 bitmap을 얻고싶어서.
        for(int i = 0; i < millisecond; i+=1000){
            // getFrameAtTime 함수는 i 라는 타임에 bitmap을 얻어와준다.(
            // getFrameAtTime의 첫번째 인자의 unit 은 microsecond이다.
            // 그래서 1000을 곱해주었다.
            bitmap = retriever.getFrameAtTime(i*1000,MediaMetadataRetriever.OPTION_CLOSEST);
            bitmapArrayList.add(bitmap);
        }
        // retreiver를 다 사용했다면 release를 해주어야한다.
        retriever.release();
        // Thread start
        thread = new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    saveFrames(bitmapArrayList);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

    
    public void saveFrames(ArrayList<Bitmap> saveBitmap) throws IOException{
        String folder = Environment.getExternalStorageDirectory().toString();
        File saveFolder = new File(folder + "/");
        if(!saveFolder.exists()){
            saveFolder.mkdirs();
        }
        int i = 1;
        for (Bitmap b : saveBitmap){
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                b.compress(Bitmap.CompressFormat.JPEG, 40, bytes);
                File f = new File(saveFolder,("filename"+i+".jpg"));

                f.createNewFile();
                FileOutputStream fo = new FileOutputStream(f);
                fo.write(bytes.toByteArray());

                fo.flush();
                fo.close();
                i++;
        }
        thread.interrupt();
    }

}

2015. 7. 19.

ViewPager and FragmentStatePagerAdapter

ViewPager를 사용하기 위해서는
Android에서 제공해주는 SupportLibrary가 필요하며 android-support-v4.jar 는 기본으로 추가되어있다.

ViewPager에 이미지나 텍스트가 아닌 Fragment를 교체하여 사용할 것이기때문에
Adapter는 FragmentStatePagerAdapter 를 사용할 것이다.

FragmentStatePagerAdapter 를 사용하려면
Gradle dependencies 에 

compile 'com.android.support:support-v13:22.2.0'


support library v13 을 추가해준다.


사실상 이부분이 애를 많이 먹었던 부분인데,
우리가 일반적으로 사용하는 Fragment는 android native class 이다.



Fragment는 

1. android.app.Fragment 
2. android.support.v4.Fragment






두가지로 나뉜다.

1번의 경우가 일반적으로 사용하는 Fragment이며
2번의 경우는 API11 이전버전을 지원하려고 하는 앱일경우 사용해준다.

여기서 중요한 것은
FragmentStatePagerAdapter도 support v4 에 있다.
하지만 android.app.Fragment로 사용하려면 버전을 맞춰야하기때문에
dependencies에 v13 library 을 추가해주는것이다.


ViewPager를 사용하려면 xml 에 아래 code를 추가하면된다.

<android.support.v4.view.ViewPager    android:layout_width="match_parent"    android:layout_height="match_parent"    android:layout_weight="1"    android:id="@+id/guide_pager"/>


아래는 ViewPager가 올라가게되는 Fragment이다.

import android.app.Fragment;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;


public class TestFragment extends Fragment implements View.OnClickListener {

    private Button exitButton;
    private ViewPager viewPager;
    private PagerAdapter pagerAdapter;
    private LinearLayout indicatorLinearLayout;
    private View view;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.main_fragment,container,false);

        initialize();
        return view;
    }
    private void initialize(){
        viewPager       = (ViewPager)view.findViewById(R.id.guide_pager);
        pagerAdapter    = new PagerAdapter(getFragmentManager());
        viewPager.setAdapter(pagerAdapter);
        exitButton      = (Button)view.findViewById(R.id.exit_guide_button);
        exitButton.setOnClickListener(this);
        indicatorLinearLayout = (LinearLayout)view.findViewById(R.id.indicator_linearlayout);
     
     
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
             
            }

            @Override
            public void onPageSelected(int position) {
                // 이 callback method는 Page가 바뀔때마다 호출된다
                // position 값은 첫페이지부터 0,1,2,,,,순서로 받아온다.
                // 아래코드는 position과 현재페이지를 알 수 있는 indicator 코드이다.
                // linearlayout에 3개의 imageview를 만들어 놓았다.
                for (int i = 0; i < indicatorLinearLayout.getChildCount(); i++) {
                    if (i == position) {
                        indicatorLinearLayout.getChildAt(i).setBackgroundResource(R.drawable.page_select);
                    } else {
                        indicatorLinearLayout.getChildAt(i).setBackgroundResource(R.drawable.page_not);
                    }
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.exit_button:
                // fragment 종료
                getFragmentManager().beginTransaction().remove(this).commit();
                break;
        }
    }
    // 핵심코드
    // v13 version으로 상속을 받았다.
    private class PagerAdapter extends android.support.v13.app.FragmentStatePagerAdapter {
        public PagerAdapter(android.app.FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Fragment를 생성해준다.
            // 실질적으로 Viewpager에 보여질 fragment를 생성하는 것이다.
            return ViewPagerFragment.create(position);
        }

        @Override
        public int getCount() {
            return 3;
        }
    }
}

아래코드는 실질적으로 Viewpager에 보여질 Fragment code이다.


import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;


public class ViewPagerFragment extends Fragment {
    private int mPageNumber;
    private View guideView;
    private LinearLayout linearLayout;
    private int [] imageArray = {R.drawable.image_0,R.drawable.image_1,R.drawable.image_2};

    public static ViewPagerFragment create(int pageNumber) {
        ViewPagerFragment fragment = new ViewPagerFragment();
            Bundle args = new Bundle();
            args.putInt("page", pageNumber);
            fragment.setArguments(args);
            return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPageNumber = getArguments().getInt("page");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_page, container, false);
        linearLayout = (LinearLayout)rootView.findViewById(R.id.pager_linearlayout);

        linearLayout.setBackgroundResource(imageArray[mPageNumber]);

        return rootView;
    }

}