Created
November 29, 2015 23:57
-
-
Save flieks/1ca7c77c7fe525c2d86f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Does the actual work for encoding frames from buffers of byte[]. | |
*/ | |
private void doEncodeDecodeVideoFromBuffer(MediaCodec encoder, int encoderColorFormat, | |
MediaCodec decoder, MediaExtractor extractor) { | |
final int TIMEOUT_USEC = 5000; | |
ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers(); | |
ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); | |
ByteBuffer[] decoderInputBuffers = null; | |
ByteBuffer[] decoderOutputBuffers = null; | |
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); | |
MediaFormat decoderOutputFormat = null; | |
int generateIndex = 0; | |
int checkIndex = 0; | |
int badFrames = 0; | |
boolean decoderConfigured = false; | |
// The size of a frame of video data, in the formats we handle, is stride*sliceHeight | |
// for Y, and (stride/2)*(sliceHeight/2) for each of the Cb and Cr channels. Application | |
// of algebra and assuming that stride==width and sliceHeight==height yields: | |
byte[] frameData = new byte[mWidth * mHeight * 3 / 2]; | |
// Just out of curiosity. | |
long rawSize = 0; | |
long encodedSize = 0; | |
// Save a copy to disk. Useful for debugging the test. Note this is a raw elementary | |
// stream, not a .mp4 file, so not all players will know what to do with it. | |
FileOutputStream outputStream = null; | |
if (DEBUG_SAVE_FILE) { | |
String test = Environment.getExternalStorageDirectory().toString(); | |
File file = new File(DEBUG_FILE_NAME_BASE); | |
if(file.exists()){ | |
String tes3t = ""; | |
} | |
String fileName = DEBUG_FILE_NAME_BASE; // + mWidth + "x" + mHeight + ".mp4"; | |
try { | |
outputStream = new FileOutputStream(file); | |
Log.d(TAG, "encoded output will be saved as " + fileName); | |
} catch (IOException ioe) { | |
Log.w(TAG, "Unable to create debug output file " + fileName); | |
throw new RuntimeException(ioe); | |
} | |
} | |
// Loop until the output side is done. | |
boolean inputDone = false; | |
boolean encoderDone = false; | |
boolean outputDone = false; | |
int offset = 0; | |
while (!outputDone) { | |
if (VERBOSE) Log.d(TAG, "loop"); | |
try{ | |
// If we're not done submitting frames, generate a new one and submit it. By | |
// doing this on every loop we're working to ensure that the encoder always has | |
// work to do. | |
// | |
// We don't really want a timeout here, but sometimes there's a delay opening | |
// the encoder device, so a short timeout can keep us from spinning hard. | |
if (!inputDone) { | |
int inputBufIndex = encoder.dequeueInputBuffer(-1); //TIMEOUT_USEC | |
if (VERBOSE) Log.d(TAG, "inputBufIndex=" + inputBufIndex); | |
if (inputBufIndex >= 0) { | |
long ptsUsec = computePresentationTime(generateIndex); | |
Counter1 ++; | |
//if (generateIndex == NUM_FRAMES) { | |
if (encoderDone) { | |
// Send an empty frame with the end-of-stream flag set. If we set EOS | |
// on a frame with data, that frame data will be ignored, and the | |
// output will be short one frame. | |
encoder.queueInputBuffer(inputBufIndex, 0, 0, ptsUsec, | |
MediaCodec.BUFFER_FLAG_END_OF_STREAM); | |
inputDone = true; | |
if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)"); | |
} else { | |
//generateFrame(generateIndex, encoderColorFormat, frameData); | |
info.offset = offset; | |
ByteBuffer inputBuf = encoderInputBuffers[inputBufIndex];//ByteBuffer.allocate(256 * 1024); // | |
info.size = extractor.readSampleData(inputBuf, offset); | |
long ptsUsec2 = 0; | |
if (info.size < 0) { | |
inputDone = true; | |
info.size = 0; | |
} | |
else | |
{ | |
ptsUsec2 = extractor.getSampleTime(); | |
} | |
// the buffer should be sized to hold one full frame | |
// assertTrue(inputBuf.capacity() >= frameData.length); | |
inputBuf.clear(); | |
inputBuf.put(frameData); | |
encoder.queueInputBuffer(inputBufIndex, 0, info.size, ptsUsec2, inputDone ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); | |
if (!inputDone) { | |
extractor.advance(); | |
} | |
// encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, ptsUsec2, 0); | |
if (VERBOSE) Log.d(TAG, "submitted frame " + generateIndex + " to enc"); | |
} | |
generateIndex++; | |
} else { | |
// either all in use, or we timed out during initial setup | |
if (VERBOSE) Log.d(TAG, "input buffer not available"); | |
} | |
} | |
} | |
catch (Exception ex){ | |
String e1 = ex.toString(); | |
} | |
try{ | |
// Check for output from the encoder. If there's no output yet, we either need to | |
// provide more input, or we need to wait for the encoder to work its magic. We | |
// can't actually tell which is the case, so if we can't get an output buffer right | |
// away we loop around and see if it wants more input. | |
// | |
// Once we get EOS from the encoder, we don't need to do this anymore. | |
if (!encoderDone) { | |
Counter2 ++; | |
int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC); | |
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { | |
// no output available yet | |
if (VERBOSE) Log.d(TAG, "no output from encoder available"); | |
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { | |
// not expected for an encoder | |
encoderOutputBuffers = encoder.getOutputBuffers(); | |
if (VERBOSE) Log.d(TAG, "encoder output buffers changed"); | |
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { | |
// not expected for an encoder | |
MediaFormat newFormat = encoder.getOutputFormat(); | |
if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat); | |
} else if (encoderStatus < 0) { | |
fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); | |
} else { // encoderStatus >= 0 | |
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; | |
if (encodedData == null) { | |
fail("encoderOutputBuffer " + encoderStatus + " was null"); | |
} | |
// It's usually necessary to adjust the ByteBuffer values to match BufferInfo. | |
encodedData.position(info.offset); | |
encodedData.limit(info.offset + info.size); | |
encodedSize += info.size; | |
if (outputStream != null) { | |
byte[] data = new byte[info.size]; | |
encodedData.get(data); | |
encodedData.position(info.offset); | |
try { | |
outputStream.write(data); | |
} catch (IOException ioe) { | |
Log.w(TAG, "failed writing debug data to file"); | |
throw new RuntimeException(ioe); | |
} | |
} | |
if ((info.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); | |
MediaFormat format = | |
MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); | |
format.setByteBuffer("csd-0", encodedData); | |
decoder.configure(format, null, | |
null, 0); | |
decoder.start(); | |
decoderInputBuffers = decoder.getInputBuffers(); | |
decoderOutputBuffers = decoder.getOutputBuffers(); | |
decoderConfigured = true; | |
if (VERBOSE) Log.d(TAG, "decoder configured (" + info.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, info.size, | |
info.presentationTimeUs, info.flags); | |
encoderDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; | |
if(encoderDone) | |
{ | |
String st = ""; | |
} | |
if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder" | |
+ (encoderDone ? " (EOS)" : "")); | |
} | |
encoder.releaseOutputBuffer(encoderStatus, false); | |
} | |
} | |
} | |
catch (Exception ex){ | |
String e1 = ex.toString(); | |
} | |
try{ | |
// 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) { | |
Counter3 ++; | |
int decoderStatus = decoder.dequeueOutputBuffer(info, 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 | |
ByteBuffer outputFrame = decoderOutputBuffers[decoderStatus]; | |
outputFrame.position(info.offset); | |
outputFrame.limit(info.offset + info.size); | |
rawSize += info.size; | |
if (info.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), | |
// info.presentationTimeUs); | |
if (!checkFrame(checkIndex++, decoderOutputFormat, outputFrame)) { | |
badFrames++; | |
} | |
} | |
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { | |
if (VERBOSE) Log.d(TAG, "output EOS"); | |
outputDone = true; | |
} | |
decoder.releaseOutputBuffer(decoderStatus, false /*render*/); | |
} | |
} | |
} | |
catch (Exception ex){ | |
String e1 = ex.toString(); | |
} | |
} | |
if (VERBOSE) Log.d(TAG, "decoded " + checkIndex + " frames at " | |
+ mWidth + "x" + mHeight + ": raw=" + rawSize + ", enc=" + encodedSize); | |
if (outputStream != null) { | |
try { | |
outputStream.close(); | |
} catch (IOException ioe) { | |
Log.w(TAG, "failed closing debug file"); | |
throw new RuntimeException(ioe); | |
} | |
} | |
// if (badFrames != 0) { | |
// fail("Found " + badFrames + " bad frames"); | |
// } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment