我们知道mp4文件是由音频轨和视频轨组成的,我们现在提取出aac文件,然后转为pcm。
其实mp4的音频轨和aac的音频轨转化为pcm操作是一样的。都是找到音频轨的索引index,然后取数据转码。
这里要设计到几个api
@NonNull
public static MediaCodec createDecoderByType(@NonNull String type)
throws IOException {
return new MediaCodec(type, true /* nameIsType */, false /* encoder */);
}
这个函数我们会根据文件的类型,创建不同的解码器
文件类型又是如何获取的呢?
我们会通过?
MediaExtractor中的
for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
MediaFormat format = mExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
Log.d(TAG, "mime=" + mime);
if (mime.startsWith("audio/")) {
audioTrack = i;
hasAudio = true;
mFormat = format;
break;
}
}
我们通过一个for循环,从多媒体文件当中通过getTrackFormat选择我们需要的音频轨
/**
* Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
* {@link #getSampleTime} only retrieve information for the subset of tracks
* selected.
* Selecting the same track multiple times has no effect, the track is
* only selected once.
*/
public native void selectTrack(int index);
/**
* Retrieve the current encoded sample and store it in the byte buffer
* starting at the given offset.
* <p>
* <b>Note:</b>As of API 21, on success the position and limit of
* {@code byteBuf} is updated to point to the data just read.
* @param byteBuf the destination byte buffer
* @return the sample size (or -1 if no more samples are available).
*/
public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);
我们通过selectTrack设置我们要exector的轨道,通过readSampleData读取我们压缩的数据。
解码过程我们要用到几个api
/**
* Returns the index of an input buffer to be filled with valid data
* or -1 if no such buffer is currently available.
* This method will return immediately if timeoutUs == 0, wait indefinitely
* for the availability of an input buffer if timeoutUs < 0 or wait up
* to "timeoutUs" microseconds if timeoutUs > 0.
* @param timeoutUs The timeout in microseconds, a negative timeout indicates "infinite".
* @throws IllegalStateException if not in the Executing state,
* or codec is configured in asynchronous mode.
* @throws MediaCodec.CodecException upon codec error.
*/
public final int dequeueInputBuffer(long timeoutUs)
可以理解为native层有一个buffer数组,这个函数从buffer数组中返回当前可用的数组索引。
/**
* Returns a {@link java.nio.Buffer#clear cleared}, writable ByteBuffer
* object for a dequeued input buffer index to contain the input data.
*
* After calling this method any ByteBuffer or Image object
* previously returned for the same input index MUST no longer
* be used.
*
* @param index The index of a client-owned input buffer previously
* returned from a call to {@link #dequeueInputBuffer},
* or received via an onInputBufferAvailable callback.
*
* @return the input buffer, or null if the index is not a dequeued
* input buffer, or if the codec is configured for surface input.
*
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
*/
@Nullable
public ByteBuffer getInputBuffer(int index)
返回索引指示的buffer
如何将数据写入buffer呢
我们通过MediaExtractor的api将数据写入buffer中
public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);
写入数据后,我们需要将buffer入队。通过下面的函数?
public final void queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags)
将当前index对应的buffer入队,这样mediaCodec就可以开始解码了。
上面写了如何给解码器输入数据,那么如何从解码器读取数据呢?
public ByteBuffer getOutputBuffer(int index)
这个index和我们上面使用的index是一个,通过这里我们可以猜测到MediaCodec的底层用两个buffer数组,一个in一个out,数组大小是一样的。输入和输出是一一对应的。
我们buffer数据使用完后,要释放空间
public final void releaseOutputBuffer(int index, boolean render)
下面是完整的代码
package com.yuanxuzhen.androidmedia.decode;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import com.yuanxuzhen.androidmedia.demux.AudioMediaInfo;
import com.yuanxuzhen.androidmedia.demux.MediaUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class AacDecoder {
public static final int ERROR_INPUT_INVALID = 100;
public static final int ERROR_OUTPUT_FAILED = 200;
public static final int ERROR_OPEN_CODEC = 300;
public static final int OK = 0;
private static final int TIMEOUT_USEC = 0;
public String TAG = "AAC_DECODER";
private MediaExtractor mExtractor;
private MediaFormat mFormat;
private FileOutputStream mFos;
private MediaCodec mDecoder;
private ByteBuffer[] mInputBuffers;
private ByteBuffer[] mOutputBuffers;
private boolean mDecodeEnd = false;
public int decode(String audioPath, String pcmPath){
int ret;
AudioMediaInfo audioMediaInfo = MediaUtil.getAudioMeidaInfo(audioPath);
Log.e(TAG, "decode " + audioMediaInfo);
if (OK != (ret = openInput(audioPath))) {
return ret;
}
if (OK != (ret = openOutput(pcmPath))) {
return ret;
}
if (OK != (ret = openCodec(mFormat))) {
return ret;
}
mDecodeEnd = false;
while (!mDecodeEnd) {
if (OK != (ret = decode(mDecoder, mExtractor))) {
Log.d(TAG, "decode failed, ret=" + ret);
break;
}
}
close();
Log.d(TAG, "decode end" + ret);
return ret;
}
private int decode(MediaCodec codec, MediaExtractor extractor) {
Log.d(TAG, "decode");
int inputIndex = codec.dequeueInputBuffer(TIMEOUT_USEC);
if (inputIndex >= 0) {
ByteBuffer inputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
inputBuffer = codec.getInputBuffer(inputIndex);
} else {
inputBuffer = mInputBuffers[inputIndex];
}
inputBuffer.clear();
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {//read end
codec.queueInputBuffer(inputIndex, 0, 0, 0L,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
codec.queueInputBuffer(inputIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {//TIMEOUT
Log.d(TAG, "INFO_TRY_AGAIN_LATER");//TODO how to declare this info
return OK;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
Log.d(TAG, "output format changed");
return OK;
} else if (outputIndex < 0) {
Log.d(TAG, "outputIndex=" + outputIndex);
return OK;
} else {
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
outputBuffer = codec.getOutputBuffer(outputIndex);
} else {
outputBuffer = mOutputBuffers[outputIndex];
}
byte[] buffer = new byte[bufferInfo.size];
outputBuffer.get(buffer);
try {
Log.d(TAG, "output write, size="+ bufferInfo.size);
mFos.write(buffer);
mFos.flush();
} catch (IOException e) {
e.printStackTrace();
return ERROR_OUTPUT_FAILED;
}
codec.releaseOutputBuffer(outputIndex, false);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
mDecodeEnd = true;
}
}
return OK;
}
private int checkPath(String path) {
if (path == null || path.isEmpty()) {
Log.d(TAG, "invalid path, path is empty");
return ERROR_INPUT_INVALID;
}
File file = new File(path);
if (!file.isFile()) {
Log.d(TAG, "path is not a file, path:" + path);
return ERROR_INPUT_INVALID;
} else if (!file.exists()) {
Log.d(TAG, "file not exists, path:" + path);
return ERROR_INPUT_INVALID;
} else {
Log.d(TAG, "path is a file, path:" + path);
}
return OK;
}
private int openInput(String audioPath) {
Log.d(TAG, "openInput audioPath:" + audioPath);
int ret;
if (OK != (ret = checkPath(audioPath))) {
return ret;
}
mExtractor = new MediaExtractor();
int audioTrack = -1;
boolean hasAudio = false;
try {
mExtractor.setDataSource(audioPath);
for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
MediaFormat format = mExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
Log.d(TAG, "mime=" + mime);
if (mime.startsWith("audio/")) {
audioTrack = i;
hasAudio = true;
mFormat = format;
break;
}
}
if (!hasAudio) {
Log.d(TAG, "input contain no audio");
return ERROR_INPUT_INVALID;
}
mExtractor.selectTrack(audioTrack);
} catch (IOException e) {
return ERROR_INPUT_INVALID;
}
return OK;
}
private int openOutput(String outputPath) {
Log.d(TAG, "openOutput outputPath:" + outputPath);
try {
mFos = new FileOutputStream(outputPath);
} catch (IOException e) {
return ERROR_OUTPUT_FAILED;
}
return OK;
}
private int openCodec(MediaFormat format) {
Log.d(TAG, "openCodec, format mime:" + format.getString(MediaFormat.KEY_MIME));
try {
mDecoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
} catch (IOException e) {
e.printStackTrace();
return ERROR_OPEN_CODEC;
}
mDecoder.configure(format, null, null, 0);
mDecoder.start();
if (Build.VERSION.SDK_INT < 21) {
mInputBuffers = mDecoder.getInputBuffers();
mOutputBuffers = mDecoder.getOutputBuffers();
}
return OK;
}
private void close() {
mExtractor.release();
mDecoder.stop();
mDecoder.release();
try {
mFos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
gitee地址
https://gitee.com/creat151/android-media.git
|