MediaCodec和MediaExtractor的理解
Understanding of MediaCodec and MediaExtractor
我想对音频文件做一些处理而不播放它们,只是数学。我怀疑我是否做对了并且有几个问题。我读了一些例子,但其中大部分是关于视频流的,根本没有处理原始数据。
我准备了一个mp3文件,有2个相同的声道,即立体声,但左右声道相同。解码后,我希望获得具有相同数字对的缓冲区,因为 PCM-16 交替存储通道样本,如 {L
R
L
R
L
R
...},对吧?例如:
{105
105
601
601
-243
-243
-484
-484
...} .
但是我得到了一对接近但不相等的数字:
{-308
-264
-1628
-1667
-2568
-2550
-4396
-4389
}
mp3 算法对相同值的编码是否不同或为什么?
我想以 1024 个样本为一组处理数据。如果没有足够的样本用于另一包,我想将其余的保存到下一批原始数据(参见代码中的 mExcess
)。是否保证订单会被保留?
我曾经把"sample"理解为音频数据的每一个值。在这里我看到了 MediaExtractor::readSampleData
和 MediaExtractor::advance
方法。第一个returns~2000个值,在描述第二个时说"Advance to the next sample"。这只是命名的重叠吗?我看到了几个示例,其中这些方法在循环中成对调用。我的用法正确吗?
这是我的代码:
public static void foo(String filepath) throws IOException {
final int SAMPLES_PER_CHUNK = 1024;
MediaExtractor mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(filepath);
MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
mediaExtractor.release();
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
String codecName = mediaCodecList.findDecoderForFormat(mediaFormat);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 0); // MediaCodec crashes with JNI
// error if FRAME_RATE is null
MediaCodec mediaCodec = MediaCodec.createByCodecName(codecName);
mediaCodec.setCallback(new MediaCodec.Callback() {
private MediaExtractor mExtractor;
private short[] mExcess;
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
if (mExtractor == null) {
mExtractor = new MediaExtractor();
try {
mExtractor.setDataSource(filepath);
mExtractor.selectTrack(0);
} catch (IOException e) {
e.printStackTrace();
}
mExcess = new short[0];
}
ByteBuffer in = codec.getInputBuffer(index);
in.clear();
int sampleSize = mExtractor.readSampleData(in, 0);
if (sampleSize > 0) {
boolean isOver = !mExtractor.advance();
codec.queueInputBuffer(
index,
0,
sampleSize,
mExtractor.getSampleTime(),
isOver ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
} else {
int helloAmaBreakpoint = 1;
}
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec,
int index,
MediaCodec.BufferInfo info) {
ByteBuffer tmp = codec.getOutputBuffer(index);
if (tmp.limit() == 0) return;
ShortBuffer out = tmp.order(ByteOrder.nativeOrder()).asShortBuffer();
// Prepend the remainder from previous batch to the new data
short[] buf = new short[mExcess.length + out.limit()];
System.arraycopy(mExcess, 0, buf, 0, mExcess.length);
out.get(buf, mExcess.length, out.limit());
final int channelCount
= codec.getOutputFormat().getInteger(MediaFormat.KEY_CHANNEL_COUNT);
for (
int offset = 0;
offset + SAMPLES_PER_CHUNK * channelCount < buf.length;
offset += SAMPLES_PER_CHUNK * channelCount) {
double[] x = new double[SAMPLES_PER_CHUNK]; // left channel
double[] y = new double[SAMPLES_PER_CHUNK]; // right channel
switch (channelCount) {
case 1: // if 1 channel then make 2 identical arrays
for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
x[i] = (double) buf[offset + i];
y[i] = (double) buf[offset + i];
}
break;
case 2: // if 2 channels then read values alternately
for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
x[i] = (double) buf[offset + i * 2];
y[i] = (double) buf[offset + i * 2 + 1];
}
break;
default:
throw new IllegalStateException("No algorithm for " + channelCount + " channels");
}
/// ... some processing ... ///
}
// Save the rest until next batch of raw data
int samplesLeft = buf.length % (SAMPLES_PER_CHUNK * channelCount);
mExcess = new short[samplesLeft];
System.arraycopy(
buf,
buf.length - samplesLeft,
mExcess,
0,
samplesLeft);
codec.releaseOutputBuffer(index, false);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) > 0) {
codec.stop();
codec.release();
mExtractor.release();
}
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
}
});
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT);
mediaCodec.configure(mediaFormat, null, null, 0);
mediaCodec.start();
}
也欢迎快速代码审查。
我完全确定为什么要这样编码,但我认为小差异在预期的公差范围内。请记住,mp3 是一种有损编解码器,解码器的输出值不会与输入值相同,只要声音表示足够接近即可。但这并不能说明为什么这两个渠道最终会有细微的不同。
是的,解码帧的各个顺序将是相同的。确切的值不匹配,但它的声音应该相似。
在 MediaExtractor 中,样本是一个编码数据包,您应该将其提供给解码器。对于 mp3,这通常是 1152 个样本(每个通道)。
我想对音频文件做一些处理而不播放它们,只是数学。我怀疑我是否做对了并且有几个问题。我读了一些例子,但其中大部分是关于视频流的,根本没有处理原始数据。
我准备了一个mp3文件,有2个相同的声道,即立体声,但左右声道相同。解码后,我希望获得具有相同数字对的缓冲区,因为 PCM-16 交替存储通道样本,如 {
L
R
L
R
L
R
...},对吧?例如:{
105
105
601
601
-243
-243
-484
-484
...} .但是我得到了一对接近但不相等的数字:
{
-308
-264
-1628
-1667
-2568
-2550
-4396
-4389
}mp3 算法对相同值的编码是否不同或为什么?
我想以 1024 个样本为一组处理数据。如果没有足够的样本用于另一包,我想将其余的保存到下一批原始数据(参见代码中的
mExcess
)。是否保证订单会被保留?我曾经把"sample"理解为音频数据的每一个值。在这里我看到了
MediaExtractor::readSampleData
和MediaExtractor::advance
方法。第一个returns~2000个值,在描述第二个时说"Advance to the next sample"。这只是命名的重叠吗?我看到了几个示例,其中这些方法在循环中成对调用。我的用法正确吗?
这是我的代码:
public static void foo(String filepath) throws IOException {
final int SAMPLES_PER_CHUNK = 1024;
MediaExtractor mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(filepath);
MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
mediaExtractor.release();
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
String codecName = mediaCodecList.findDecoderForFormat(mediaFormat);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 0); // MediaCodec crashes with JNI
// error if FRAME_RATE is null
MediaCodec mediaCodec = MediaCodec.createByCodecName(codecName);
mediaCodec.setCallback(new MediaCodec.Callback() {
private MediaExtractor mExtractor;
private short[] mExcess;
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
if (mExtractor == null) {
mExtractor = new MediaExtractor();
try {
mExtractor.setDataSource(filepath);
mExtractor.selectTrack(0);
} catch (IOException e) {
e.printStackTrace();
}
mExcess = new short[0];
}
ByteBuffer in = codec.getInputBuffer(index);
in.clear();
int sampleSize = mExtractor.readSampleData(in, 0);
if (sampleSize > 0) {
boolean isOver = !mExtractor.advance();
codec.queueInputBuffer(
index,
0,
sampleSize,
mExtractor.getSampleTime(),
isOver ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
} else {
int helloAmaBreakpoint = 1;
}
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec,
int index,
MediaCodec.BufferInfo info) {
ByteBuffer tmp = codec.getOutputBuffer(index);
if (tmp.limit() == 0) return;
ShortBuffer out = tmp.order(ByteOrder.nativeOrder()).asShortBuffer();
// Prepend the remainder from previous batch to the new data
short[] buf = new short[mExcess.length + out.limit()];
System.arraycopy(mExcess, 0, buf, 0, mExcess.length);
out.get(buf, mExcess.length, out.limit());
final int channelCount
= codec.getOutputFormat().getInteger(MediaFormat.KEY_CHANNEL_COUNT);
for (
int offset = 0;
offset + SAMPLES_PER_CHUNK * channelCount < buf.length;
offset += SAMPLES_PER_CHUNK * channelCount) {
double[] x = new double[SAMPLES_PER_CHUNK]; // left channel
double[] y = new double[SAMPLES_PER_CHUNK]; // right channel
switch (channelCount) {
case 1: // if 1 channel then make 2 identical arrays
for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
x[i] = (double) buf[offset + i];
y[i] = (double) buf[offset + i];
}
break;
case 2: // if 2 channels then read values alternately
for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
x[i] = (double) buf[offset + i * 2];
y[i] = (double) buf[offset + i * 2 + 1];
}
break;
default:
throw new IllegalStateException("No algorithm for " + channelCount + " channels");
}
/// ... some processing ... ///
}
// Save the rest until next batch of raw data
int samplesLeft = buf.length % (SAMPLES_PER_CHUNK * channelCount);
mExcess = new short[samplesLeft];
System.arraycopy(
buf,
buf.length - samplesLeft,
mExcess,
0,
samplesLeft);
codec.releaseOutputBuffer(index, false);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) > 0) {
codec.stop();
codec.release();
mExtractor.release();
}
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
}
});
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT);
mediaCodec.configure(mediaFormat, null, null, 0);
mediaCodec.start();
}
也欢迎快速代码审查。
我完全确定为什么要这样编码,但我认为小差异在预期的公差范围内。请记住,mp3 是一种有损编解码器,解码器的输出值不会与输入值相同,只要声音表示足够接近即可。但这并不能说明为什么这两个渠道最终会有细微的不同。
是的,解码帧的各个顺序将是相同的。确切的值不匹配,但它的声音应该相似。
在 MediaExtractor 中,样本是一个编码数据包,您应该将其提供给解码器。对于 mp3,这通常是 1152 个样本(每个通道)。