【发布时间】:2013-09-22 08:30:37
【问题描述】:
我正在修改 Android Framework example 以将 MediaCodec 生成的基本 AAC 流打包成独立的 .mp4 文件。我正在使用一个 MediaMuxer 实例,其中包含一个由 MediaCodec 实例生成的 AAC 轨道。
但是,我在调用mMediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo) 时总是会收到一条错误消息:
E/MPEG4Writer﹕timestampUs 0 < lastTimestampUs XXXXX for Audio track
当我在mCodec.queueInputBuffer(...) 中对原始输入数据进行排队时,我根据框架示例提供 0 作为时间戳值(我还尝试使用具有相同结果的单调递增时间戳值。我已成功将原始相机帧编码为h264/mp4 文件,方法相同)。
最相关的sn-p:
private static void testEncoder(String componentName, MediaFormat format, Context c) {
int trackIndex = 0;
boolean mMuxerStarted = false;
File f = FileUtils.createTempFileInRootAppStorage(c, "aac_test_" + new Date().getTime() + ".mp4");
MediaCodec codec = MediaCodec.createByCodecName(componentName);
try {
codec.configure(
format,
null /* surface */,
null /* crypto */,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IllegalStateException e) {
Log.e(TAG, "codec '" + componentName + "' failed configuration.");
}
codec.start();
try {
mMediaMuxer = new MediaMuxer(f.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException ioe) {
throw new RuntimeException("MediaMuxer creation failed", ioe);
}
ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
int numBytesSubmitted = 0;
boolean doneSubmittingInput = false;
int numBytesDequeued = 0;
while (true) {
int index;
if (!doneSubmittingInput) {
index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
if (numBytesSubmitted >= kNumInputBytes) {
Log.i(TAG, "queueing EOS to inputBuffer");
codec.queueInputBuffer(
index,
0 /* offset */,
0 /* size */,
0 /* timeUs */,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
if (VERBOSE) {
Log.d(TAG, "queued input EOS.");
}
doneSubmittingInput = true;
} else {
int size = queueInputBuffer(
codec, codecInputBuffers, index);
numBytesSubmitted += size;
if (VERBOSE) {
Log.d(TAG, "queued " + size + " bytes of input data.");
}
}
}
}
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
} else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = codec.getOutputFormat();
trackIndex = mMediaMuxer.addTrack(newFormat);
mMediaMuxer.start();
mMuxerStarted = true;
} else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
} else {
// Write to muxer
ByteBuffer encodedData = codecOutputBuffers[index];
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + index +
" was null");
}
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// The codec config data was pulled out and fed to the muxer when we got
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
info.size = 0;
}
if (info.size != 0) {
if (!mMuxerStarted) {
throw new RuntimeException("muxer hasn't started");
}
// adjust the ByteBuffer values to match BufferInfo (not needed?)
encodedData.position(info.offset);
encodedData.limit(info.offset + info.size);
mMediaMuxer.writeSampleData(trackIndex, encodedData, info);
if (VERBOSE) Log.d(TAG, "sent " + info.size + " audio bytes to muxer with pts " + info.presentationTimeUs);
}
codec.releaseOutputBuffer(index, false);
// End write to muxer
numBytesDequeued += info.size;
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
if (VERBOSE) {
Log.d(TAG, "dequeued output EOS.");
}
break;
}
if (VERBOSE) {
Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
}
}
}
if (VERBOSE) {
Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
+ "dequeued " + numBytesDequeued + " bytes.");
}
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int inBitrate = sampleRate * channelCount * 16; // bit/sec
int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
float desiredRatio = (float)outBitrate / (float)inBitrate;
float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;
if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
Log.w(TAG, "desiredRatio = " + desiredRatio
+ ", actualRatio = " + actualRatio);
}
codec.release();
mMediaMuxer.stop();
mMediaMuxer.release();
codec = null;
}
更新:我发现了一个我认为在MediaCodec中的根本症状。:
我将presentationTimeUs=1000 发送到queueInputBuffer(...),但在调用MediaCodec.dequeueOutputBuffer(info, timeoutUs) 后收到info.presentationTimeUs= 33219。 fadden 留下了与此行为相关的有用评论。
【问题讨论】:
-
听起来 MediaMuxer 正在获取零和非零时间戳。您是否尝试在每次
writeSampleData调用时记录info的内容以验证它是否具有您期望的值? -
我记录了输出,确实,在抛出错误之前,信息包含非零
presentationTimeUs。这个值与提供给queueInputBuffer(...)的值有何不同? -
我不知道。该值是否看起来是与先前值的固定偏移量 - 即每次都是相同的值,但如果您为时间戳传递一个恒定的非零值,它会改变?
-
是的,无法解释的时间戳总是与我提供的固定时间戳不同:23219。
-
最好的猜测:编码器正在对输出做一些事情——可能将一个输入数据包分成两个输出数据包——这需要它合成一个时间戳。它获取数据包开始的时间戳,并根据比特率和字节数添加一个值。如果您生成的时间戳具有相当正确的呈现时间,则在生成“中间”时间戳时,您不应该看到它倒退。
标签: android android-mediacodec