mp4parser를 사용해서 오디오파일을 합쳐보자!

Posted by ITPangPang
2016.09.04 16:36 안드로이드(android)/오픈소스관련


mp4parser를 사용해서 

오디오파일을 합쳐보자!


ㆍ 이번에는 간단하게 mp4parser라는 라이브러리를

    추가해서 오디오 파일을 합치는 방법을 알아보겠습니다.


ㆍ 이번에 MediaRecoder를 사용해서 녹음기를 한번 만들어 

    보려고 했는데 일시중지 기능이 없어서 알아봤더니

    파일을 합쳐서 기능을 구현해야 된다고 알아냈습니다


    예를 들어서 녹음중에 일시중지를 3번 눌렀다면 실제로는

    파일을 3개 만들어서 파일을 합쳐버리는 작업을 해야합니다.


ㆍ 뭐 간단한 녹음기능을 구현하려던 것이었어서 관련된 라이브러리를 찾아보니 

    mp4parser를 사용하면 쉽게 구현할 수 있었습니다.




간단한 테스트를 위해


mp4 파일 2개를 녹음해서

준비했습니다



뭐 MediaRecoder로

녹음한 파일입니다..


test1.mp4 : 1분 46초짜리 file

test2.mp4 : 2초짜리 file

입니다


먼저,

안드로이드 스튜디오에서

사용하기 위해서

build.gradle에

라이브러리를 추가해줍니다


compile 'com.googlecode.mp4parser:isoparser:1.0.5.4'



이런식으로 들어가면 되겠죠?


그 다음은 버튼을 누르면

파일을 합쳐주기 위해서

Layout에 버튼을 하나 놓습니다

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.tistory.itpangpang.mp4parserex.MainActivity">

<Button
android:id="@+id/btn_merge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Merge" />
</RelativeLayout>


이런식으로 버튼을 하나 놓고

이제 코드를 작성해줍니다


먼저 파일경로를 String에 담는다


String inputFirst = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test1.mp4";
String inputSecond = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test2.mp4";
String outputFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/output.mp4";


전 고정된 파일을 

사용할것이기 때문에

이런식으로 넣어놧습니다

(실제 녹음기 구현할때는 저장시점을 파일명

뒤에 붙여서 구분하는 식으로 구현하였습니다)


Movie의 크기를 정해주고 파일을 담는다


Movie[] inMovies = new Movie[2];
try
{
inMovies = new Movie[]{MovieCreator.build(inputFirst),MovieCreator.build(inputSecond),};
}
catch (IOException e)
{
e.printStackTrace();
}


뭐 살짝 여기서 주의할점까진 아닌데

Movie를 import할때


기본적으로 android.graphics라는

클래스가 있기 때문에

아무생각 없이 밑에껄로 import하면

MovieCreator.build 부분이 빨간줄 뜨는데

이걸 보고 코드가 잘못됬다고 헤맬 수 있는

가능성이 있기 때문에...

꼭 확인하고 import 넣어주세요


그 다음 오디오 트랙에 차례대로 담아줍니다


List<Track> audioTracks = new LinkedList<Track>();
for (Movie m : inMovies)
{
for (Track t : m.getTracks())
{
if (t.getHandler().equals("soun"))
{
audioTracks.add(t);
}
}
}


List를 하나 생성하고

향상된 for문을 사용하여 

inMovies에 있는 파일들에

접근하여 차례대로 add해줍니다

.equals("soun");

이 부분은 오디오파일을 빼오는 부분으로

똑같이 적어주시면 됩니다


사용하는 이유는 이 라이브러리로 비디오파일도

합칠 수 있는데 비디오파일과 오디오파일을

구분하기 위해서 사용합니다


결과를 담을 Movie를 하나 더 생성해서 합친 결과를 담는다


Movie output = new Movie();
if (audioTracks.size() > 0)
{
try
{
output.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}
catch (IOException e)
{
e.printStackTrace();
}
}

Container out = new DefaultMp4Builder().build(output);

FileChannel fc = null;
try
{
fc = new FileOutputStream(new File(outputFile)).getChannel();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}

try
{
out.writeContainer(fc);
}
catch (IOException e)
{
e.printStackTrace();
}
try
{
fc.close();
}
catch (IOException e)
{
e.printStackTrace();
}


대충 살펴보면

audioTracks의 사이즈가

0보다 클때, 즉 파일이 존재할때

Track을 추가합니다.

AppendTrack으로 뒤에 붙이는 식이 되겠죠


완성될 파일이 생성되는 경로를

적어주기 위해 FileOutputStream을

사용하는 모습도 보이고


그다음 Container를 사용해서

writeContainer의 과정의

작업을 거치게 되면 최종완성이

됩니다.


코드를 작성한 후에

실행을 하기전에


Manifest에 EXTERNAL_STORAGE

퍼미션을 넣어주는 것을 깜빡하면

안됩니다


결과를 확인해보면



1분 49초짜리 output.mp4파일이

성공적으로 생성된것을

확인 할 수 있습니다.


아래는 한번 합친파일을

Movie에 추가시켜서

3개를 합쳐봤습니다


2초, 1분 46초, 1분 49초 File을

합쳐서 3분 38초짜리 File로

완성시켰습니다


테스트 코드입니다


public class MainActivity extends AppCompatActivity
{
String inputFirst = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test1.mp4";
String inputSecond = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test2.mp4";
String outputFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/output.mp4";

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button btn_merge = (Button) findViewById(R.id.btn_merge);
btn_merge.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
startMerge();
}
});
}

public void startMerge()
{
Movie[] inMovies = new Movie[2];
try
{
inMovies = new Movie[]{MovieCreator.build(inputFirst),MovieCreator.build(inputSecond),};
}
catch (IOException e)
{
e.printStackTrace();
}
List<Track> audioTracks = new LinkedList<Track>();
for (Movie m : inMovies)
{
for (Track t : m.getTracks())
{
if (t.getHandler().equals("soun"))
{
audioTracks.add(t);
}
}
}

Movie output = new Movie();
if (audioTracks.size() > 0)
{
try
{
output.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}
catch (IOException e)
{
e.printStackTrace();
}
}

Container out = new DefaultMp4Builder().build(output);

FileChannel fc = null;
try
{
fc = new FileOutputStream(new File(outputFile)).getChannel();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}

try
{
out.writeContainer(fc);
}
catch (IOException e)
{
e.printStackTrace();
}
try
{
fc.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}


녹음 적용해보려고 예제코드를

찾아보니 전부 동영상 합치는것밖에

없어서 제가 대충 단축시키고

변형해서 만든거라

코드가 깔끔하지 않을 수 있으니

이해해주세요~

저작자 표시 비영리 변경 금지
신고
이 댓글을 비밀 댓글로
    • 안녕하세요
    • 2016.09.07 15:01 신고
    안녕하세요^^
    내용 잘 봤습니다~
    저도 녹음 pause resume을 넣으려고 mp4parser 써보고있는데
    위에 처럼 고정이 아니라 파일이 몇개가 들어올지 모르는 경우에는
    Movie에 넣을때 어떻게 처리해야 하나요? Arraylist로 추가하는것 까지 했어요
    • Array로 담으셨다고 가정하면

      merge 이벤트 발생시키는 부분에서
      startMerge(recordFilePathList);
      이런식으로 호출해주시고

      public void startMerge(ArrayList<String> recordFilePathList)
      {
      Movie[] inMovies = new Movie[recordFilePathList.size()];
      try
      {
      for(int a = 0; a < recordFilePathList.size(); a++)
      {
      inMovies[a] = MovieCreator.build(recordFilePathList.get(a));
      }
      }
      이런식으로 받으시면 될 것 같습니다
      아래코드는 그대로 쓰셔도 될 거 같네요