整个过程是:从Camera中获取视频数据,对其进行编码和解码,并将其显示在Surfaceview上。
视频编码:我使用MediaCodec Surface获取数据。请参见google / grafika-https://github.com/google/grafika
视频解码:参考EncodeDecodeTest-- https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
但是有数据,它没有显示在Surfaceview上,我怀疑问题已解码。 这是我的视频编解码器代码:
/**
* Drains all pending output from the decoder, and adds it to the circular buffer.
* <p>
*/
public void drainEncoder() {
final int TIMEOUT_USEC = 0; // no timeout -- check for buffers, bail if none
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
while (true) {
int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
break;
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// not expected for an encoder
encoderOutputBuffers = mEncoder.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Should happen before receiving buffers, and should only happen once.
// The MediaFormat contains the csd-0 and csd-1 keys, which we'll need
// for MediaMuxer. It's unclear what else MediaMuxer might want, so
// rather than extract the codec-specific data and reconstruct a new
// MediaFormat later, we just grab it here and keep it around.
mEncodedFormat = mEncoder.getOutputFormat();
Log.d(TAG, "encoder output format changed: " + mEncodedFormat);
} else if (encoderStatus < 0) {
Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " +
encoderStatus);
// let's ignore it
} else {
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus +
" was null");
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// The codec config data was pulled out when we got the
// INFO_OUTPUT_FORMAT_CHANGED status. The MediaMuxer won't accept
// a single big blob -- it wants separate csd-0/csd-1 chunks --
// so simply saving this off won't work.
if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
mBufferInfo.size = 0;
}
if (mBufferInfo.size != 0) {
// adjust the ByteBuffer values to match BufferInfo (not needed?)
encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
mEncBuffer.add(encodedData, mBufferInfo.flags,
mBufferInfo.presentationTimeUs);
if (VERBOSE) {
Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, ts=" +
mBufferInfo.presentationTimeUs);
}
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// Codec config info. Only expected on first packet. One way to
// handle this is to manually stuff the data into the MediaFormat
// and pass that to configure(). We do that here to exercise the API.
assertFalse(decoderConfigured);
try {
decoder = MediaCodec.createDecoderByType(MIME_TYPE);
} catch (IOException e) {
e.printStackTrace();
}
MediaFormat format =
MediaFormat.createVideoFormat(MIME_TYPE, 80, 80);
format.setByteBuffer("csd-0", encodedData);
decoder.configure(format, mSurfaceView.getHolder().getSurface(),
null, 0);
decoder.start();
decoderInputBuffers = decoder.getInputBuffers();
decoderOutputBuffers = decoder.getOutputBuffers();
decoderConfigured = true;
if (VERBOSE)
Log.d(TAG, "decoder configured (" + mBufferInfo.size + "bytes)");
} else {
// Get a decoder input buffer, blocking until it's available.
assertTrue(decoderConfigured);
int inputBufIndex = decoder.dequeueInputBuffer(-1);
ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
inputBuf.clear();
inputBuf.put(encodedData);
decoder.queueInputBuffer(inputBufIndex, 0, mBufferInfo.size,
mBufferInfo.presentationTimeUs, mBufferInfo.flags);
// encoderDone = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
// if (VERBOSE) Log.d(TAG, "passed" + mBufferInfo.size + "bytes to decoder"
// + (encoderDone ? "(EOS)" : ""));
}
mEncoder.releaseOutputBuffer(encoderStatus, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.w(TAG, "reached end of stream unexpectedly");
break; // out of while
}
}
}
// Check for output from the decoder. We want to do this on every loop to avoid
// the possibility of stalling the pipeline. We use a short timeout to avoid
// burning CPU if the decoder is hard at work but the next frame isn't quite ready.
//
// If we're decoding to a Surface, we'll get notified here as usual but the
// ByteBuffer references will be null. The data is sent to Surface instead.
if (decoderConfigured) {
int decoderStatus = decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
if (VERBOSE) Log.d(TAG, "no output from decoder available");
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// The storage associated with the direct ByteBuffer may already be unmapped,
// so attempting to access data through the old output buffer array could
// lead to a native crash.
if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
decoderOutputBuffers = decoder.getOutputBuffers();
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// this happens before the first frame is returned
decoderOutputFormat = decoder.getOutputFormat();
if (VERBOSE) Log.d(TAG, "decoder output format changed:" +
decoderOutputFormat);
} else if (decoderStatus < 0) {
fail("unexpected result from deocder.dequeueOutputBuffer:" + decoderStatus);
} else { // decoderStatus >= 0
if (!toSurface) {
ByteBuffer outputFrame = decoderOutputBuffers[decoderStatus];
outputFrame.position(mBufferInfo.offset);
outputFrame.limit(mBufferInfo.offset + mBufferInfo.size);
rawSize += mBufferInfo.size;
if (mBufferInfo.size == 0) {
if (VERBOSE) Log.d(TAG, "got empty frame");
} else {
// if (VERBOSE) Log.d(TAG, "decoded, checking frame" + checkIndex);
// assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
// mBufferInfo.presentationTimeUs);
// if (!checkFrame(checkIndex++, decoderOutputFormat, outputFrame)) {
// badFrames++;
// }
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
if (VERBOSE) Log.d(TAG, "output EOS");
outputDone = true;
}
decoder.releaseOutputBuffer(decoderStatus, false /*render*/);
} else {
if (VERBOSE) Log.d(TAG, "surface decoder given buffer" + decoderStatus +
"(size=" + mBufferInfo.size + ")");
rawSize += mBufferInfo.size;
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
if (VERBOSE) Log.d(TAG, "output EOS");
outputDone = true;
}
boolean doRender = (mBufferInfo.size != 0);
// As soon as we call releaseOutputBuffer, the buffer will be forwarded
// to SurfaceTexture to convert to a texture. The API doesn't guarantee
// that the texture will be available before the call returns, so we
// need to wait for the onFrameAvailable callback to fire.
decoder.releaseOutputBuffer(decoderStatus, doRender);
// if (doRender) {
// if (VERBOSE) Log.d(TAG, "awaiting frame" + checkIndex);
// assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
// info.presentationTimeUs);
// outputSurface.awaitNewImage();
// outputSurface.drawImage();
// if (!checkSurfaceFrame(checkIndex++)) {
// badFrames++;
// }
}
}
}
}
补充:解码时此值始终为-1,应在不包含数据的情况下传递给解码器。
int decoderStatus = decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
答案 0 :(得分:0)
问题解决了 1.配置解码器的csd-0 csd-1 2.先前传递给解码器的数据不正确。
下面是新代码:
public void drainEncoder() {
final int TIMEOUT_USEC = 0; // no timeout -- check for buffers, bail if none
int pos = 0;
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
while (true) {
int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
break;
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// not expected for an encoder
encoderOutputBuffers = mEncoder.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Should happen before receiving buffers, and should only happen once.
// The MediaFormat contains the csd-0 and csd-1 keys, which we'll need
// for MediaMuxer. It's unclear what else MediaMuxer might want, so
// rather than extract the codec-specific data and reconstruct a new
// MediaFormat later, we just grab it here and keep it around.
mEncodedFormat = mEncoder.getOutputFormat();
Log.d(TAG, "encoder output format changed: " + mEncodedFormat);
} else if (encoderStatus < 0) {
Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " +
encoderStatus);
// let's ignore it
} else { // >= 0
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
byte[] outData = new byte[mBufferInfo.size];
encodedData.get(outData);
if (arrInfo != null) {
System.arraycopy(outData, 0, arrOutput, pos, outData.length);
pos += outData.length;
} else {
ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);
if (spsPpsBuffer.getInt() == 0x00000001) {
arrInfo = new byte[outData.length];
System.arraycopy(outData, 0, arrInfo, 0, outData.length);
findSpsAndPps(arrInfo);
}
}
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus +
" was null");
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, 120, 120);
format.setByteBuffer("csd-0", ByteBuffer.wrap(arrSps));
format.setByteBuffer("csd-1", ByteBuffer.wrap(arrPps));
decoder.configure(format, mSurfaceView.getHolder().getSurface(), null, 0);
decoder.start();
decoderConfigured = true;
} else {
ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
int inputBufferIndex = decoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = decoderInputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(arrOutput, 0, pos);
decoder.queueInputBuffer(inputBufferIndex, 0, pos, System.currentTimeMillis(), 0);
}
}
mEncoder.releaseOutputBuffer(encoderStatus, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.w(TAG, "reached end of stream unexpectedly");
break; // out of while
}
}
if (decoderConfigured) {
int decoderStatus = decoder.dequeueOutputBuffer(mBufferInfo, 10000);
Log.i("decoderStatus", "decoderStatus =====" + decoderStatus);
if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
if (VERBOSE) Log.d(TAG, "no output from decoder available");
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = decoder.getOutputFormat();
if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
} else if (decoderStatus < 0) {
throw new RuntimeException(
"unexpected result from decoder.dequeueOutputBuffer: " +
decoderStatus);
} else { // decoderStatus >= 0
if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
" (size=" + mBufferInfo.size + ")");
boolean doRender = (mBufferInfo.size != 0);
decoder.releaseOutputBuffer(decoderStatus, doRender);
// decoder.flush(); // reset decoder state
}
}
}
}