I'm having trouble decoding and outputting h264 raw data from MediaCodec in TextureView. I get raw data in byte arrays, each of which is a NAL unit (starts with 0x00 0x00 0x00 0x01), there are also SAL and PPS NAL units at constant intervals. When new data appears, I put it in LinkedBlockingQueue:
public void pushData(byte[] videoBuffer) {
dataQueue.add(videoBuffer);
if (!decoderConfigured) {
if (dataQueue.peek() != null && checkIfParameterSet(dataQueue.peek(), SPSID)) {
if (dataQueue.size() == 2) {
Iterator<byte[]> iterator = dataQueue.iterator();
String videoFormat = "video/avc";
MediaFormat format = MediaFormat.createVideoFormat(videoFormat, width, height);
format.setString("KEY_MIME", videoFormat);
format.setByteBuffer("csd-0", ByteBuffer.wrap(concat(dataQueue.peek(), iterator.next())));
try {
decoder = MediaCodec.createDecoderByType(videoFormat);
} catch (IOException e) {
e.printStackTrace();
}
decoder.configure(format, mOutputSurface, null, 0);
decoder.start();
inputBuffer = decoder.getInputBuffers();
decoderConfigured = true;
}
} else {
dataQueue.clear();
}
}
}
As you can see, there is also a decoder configuration here. This is done when the first SPS + PPS is displayed in the queue. The main part works in a loop while:
private void work() {
while(true) {
if (decoderConfigured) {
byte[] chunk = dataQueue.poll();
if (chunk != null) {
if (checkIfParameterSet(chunk, SPSID)) {
if (!SPSPushed) {
SPSPushed = true;
queueInputBuffer(chunk);
}
} else if (checkIfParameterSet(chunk, PPSID)) {
if (!PPSPushed) {
PPSPushed = true;
queueInputBuffer(chunk);
}
} else {
queueInputBuffer(chunk);
}
}
int decoderStatus = decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
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 {
if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
" (size=" + mBufferInfo.size + ")");
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
if (VERBOSE) Log.d(TAG, "output EOS");
}
boolean doRender = (mBufferInfo.size != 0);
try {
if (doRender && frameCallback != null) {
Log.d(TAG, "Presentation time passed to frameCallback: " + mBufferInfo.presentationTimeUs);
frameCallback.preRender(mBufferInfo.presentationTimeUs);
}
decoder.releaseOutputBuffer(decoderStatus, doRender);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
And queueInputBufferit looks like this:
private void queueInputBuffer(byte[] data) {
int inIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
if (inIndex >= 0) {
inputBuffer[inIndex].clear();
inputBuffer[inIndex].put(data, 0, data.length);
decoder.queueInputBuffer(inIndex, 0, data.length, System.currentTimeMillis() * 1000, 0);
}
}
The class that completes this mechanics runs in a separate thread, similarly MoviePlayerfrom grafika . Also FrameCallbackhas SpeedControlCallbackfrom grafika.
. ( ) , , , , . ffplay, , .
, , . ( , preRender()) . , .
- ?
1
fadden, , MediaCodec. , . , . , , . , , , , ( ), MediaCodec ( , , ).
, , NAL- .
, MediaCodec, :
0x00, 0x00, 0x00, 0x01, 0x67, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x65, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x21, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x21, 0xNN, 0xNN ...
.
.
.
0x00, 0x00, 0x00, 0x01, 0x21, 0xNN, 0xNN ...
NALU , , ( 0x65), 0x21. 0x65, 0x21 ..
:
0x00, 0x00, 0x00, 0x01, 0x67, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x68, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x61, 0xNN, 0xNN ...
0x00, 0x00, 0x00, 0x01, 0x61, 0xNN, 0xNN ...
.
.
.
0x00, 0x00, 0x00, 0x01, 0x61, 0xNN, 0xNN ...
.