drcarter의 DevLog

안드로이드에서 음악을 재생할 때 가장 많이 사용하는 것으 MediaPlayer 입니다. 사용 방법도 간단하고 Added in API level 1 이 알려주다 싶이 Media 재생을 위한 api로는 가장 오래되고 많이 사용을 하죠. 그럼 Wave 파일에 대해서는 어떻게 재생을 할까요??? AudioTrack이라는 api가 존재합니다. pcm 데이터를 재생시켜줄 수 있는 api인데, 사실 mp3나 aac 같은 파일을 재생하기 위해서 Decoder로 mp3나 aac같은 파일을 다시 pcm데이터인 wave 파일로 만들어야 AudioTrack으로 재생시킬 수 있습니다. 

그럼 mp3나 aac와 같은 파일을 wave로의 Decoder는 api로 존재를 하느냐?.. API level 16인 Jelly Bean(Android 4.1) 부터 AudioCodec 이라는 api가 새로 생겼습니다. AudioCodec은 Encoder와 Decoder 기능을 같이 제공 합니다. 그래서 오디오 정보는 AudioTrack으로 재생을 할 수 있는 것이죠.

생성 방법은

MediaCodec codec = MediaCodec.createDecoderByType(type);
MediaCodec codec = MediaCodec.createEncoderByType(type);

이와 같은 방법으로 MediaCodec을 생성해서 사용할 수 있습니다.



디코딩을 통한 재생하는 메인 코드 입니다. 원리는 해당 버퍼만큼 디코딩하고 그에 해당하는 부분으 AudioTrack을 통한 write로 재생을 하는 방법입니다.
private void decodeLoop()
	{
		ByteBuffer[] codecInputBuffers;
		ByteBuffer[] codecOutputBuffers;

		mExtractor = new MediaExtractor();
		try
		{
			mExtractor.setDataSource(this.mMediaPath);
		}
		catch (Exception e)
		{
			mAudioPlayerHandler.onAudioPlayerError(AudioStreamPlayer.this);
			return;
		}

		MediaFormat format = mExtractor.getTrackFormat(0);
		String mime = format.getString(MediaFormat.KEY_MIME);
		long duration = format.getLong(MediaFormat.KEY_DURATION);
		int totalSec = (int) (duration / 1000 / 1000);
		int min = totalSec / 60;
		int sec = totalSec % 60;

		mAudioPlayerHandler.onAudioPlayerDuration(totalSec);

		Log.d(TAG, "Time = " + min + " : " + sec);
		Log.d(TAG, "Duration = " + duration);

		mMediaCodec = MediaCodec.createDecoderByType(mime);
		mMediaCodec.configure(format, null, null, 0);
		mMediaCodec.start();
		codecInputBuffers = mMediaCodec.getInputBuffers();
		codecOutputBuffers = mMediaCodec.getOutputBuffers();

		int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);

		Log.i(TAG, "mime " + mime);
		Log.i(TAG, "sampleRate " + sampleRate);

		mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_STEREO,
				AudioFormat.ENCODING_PCM_16BIT, AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO,
						AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);

		mAudioTrack.play();
		mExtractor.selectTrack(0);

		final long kTimeOutUs = 10000;
		MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
		boolean sawInputEOS = false;
		int noOutputCounter = 0;
		int noOutputCounterLimit = 50;

		while (!sawInputEOS && noOutputCounter < noOutputCounterLimit && !isForceStop)
		{
			if (!sawInputEOS)
			{
				if(isPause)
				{
					if(mState != State.Pause)
					{
						mState = State.Pause;
						
						mAudioPlayerHandler.onAudioPlayerPause();
					}
					continue;
				}
				noOutputCounter++;
				if (isSeek)
				{
					mExtractor.seekTo(seekTime * 1000 * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
					isSeek = false;
				}

				mInputBufIndex = mMediaCodec.dequeueInputBuffer(kTimeOutUs);
				if (mInputBufIndex >= 0)
				{
					ByteBuffer dstBuf = codecInputBuffers[mInputBufIndex];

					int sampleSize = mExtractor.readSampleData(dstBuf, 0);

					long presentationTimeUs = 0;

					if (sampleSize < 0)
					{
						Log.d(TAG, "saw input EOS.");
						sawInputEOS = true;
						sampleSize = 0;
					}
					else
					{
						presentationTimeUs = mExtractor.getSampleTime();

						Log.d(TAG, "presentaionTime = " + (int) (presentationTimeUs / 1000 / 1000));

						mAudioPlayerHandler.onAudioPlayerCurrentTime((int) (presentationTimeUs / 1000 / 1000));
					}

					mMediaCodec.queueInputBuffer(mInputBufIndex, 0, sampleSize, presentationTimeUs,
							sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

					if (!sawInputEOS)
					{
						mExtractor.advance();
					}
				}
				else
				{
					Log.e(TAG, "inputBufIndex " + mInputBufIndex);
				}
			}

			int res = mMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);

			if (res >= 0)
			{
				if (info.size > 0)
				{
					noOutputCounter = 0;
				}

				int outputBufIndex = res;
				ByteBuffer buf = codecOutputBuffers[outputBufIndex];

				final byte[] chunk = new byte[info.size];
				buf.get(chunk);
				buf.clear();
				if (chunk.length > 0)
				{
					mAudioTrack.write(chunk, 0, chunk.length);
					if (this.mState != State.Playing)
					{
						mAudioPlayerHandler.onAudioPlayerPlayerStart(AudioStreamPlayer.this);
					}
					this.mState = State.Playing;
				}
				mMediaCodec.releaseOutputBuffer(outputBufIndex, false);
			}
			else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
			{
				codecOutputBuffers = mMediaCodec.getOutputBuffers();

				Log.d(TAG, "output buffers have changed.");
			}
			else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
			{
				MediaFormat oformat = mMediaCodec.getOutputFormat();

				Log.d(TAG, "output format has changed to " + oformat);
			}
			else
			{
				Log.d(TAG, "dequeueOutputBuffer returned " + res);
			}
		}

		Log.d(TAG, "stopping...");

		releaseResources(true);

		this.mState = State.Stopped;
		isForceStop = true;

		if (noOutputCounter >= noOutputCounterLimit)
		{
			mAudioPlayerHandler.onAudioPlayerError(AudioStreamPlayer.this);
		}
		else
		{
			mAudioPlayerHandler.onAudioPlayerStop(AudioStreamPlayer.this);
		}
	}


사실 이 방법을 사용하는 것은 기존의 MediaPlayer를 사용해서 재생하는 방법보다는 상당히 복잡합니다.

하지만 이 방법을 사용하면 바로 pcm데이터를 통한 가공을 할 수 있다는 장점이 있습니다. MediaPlayer에서도 Visualizer를 통하여 pcm데이터를 가져올 수 있는 방법도 있습니다. 

설명보다는... 그냥 코드로~

https://github.com/drcarter/AudioStreamPlayer