1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.cts; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.media.MediaCodec; 22 import android.media.MediaCodecInfo.CodecCapabilities; 23 import android.media.MediaFormat; 24 import android.os.Bundle; 25 import android.test.AndroidTestCase; 26 import android.util.Log; 27 28 import com.android.cts.media.R; 29 30 import java.io.InputStream; 31 import java.nio.ByteBuffer; 32 33 /** 34 * Basic verification test for vp8 encoder. 35 * 36 * A raw yv12 stream is encoded and written to an IVF 37 * file, which is later decoded by vp8 decoder to verify 38 * frames are at least decodable. 39 */ 40 public class Vp8EncoderTest extends AndroidTestCase { 41 42 private static final String TAG = "VP8EncoderTest"; 43 private static final String VP8_MIME = "video/x-vnd.on2.vp8"; 44 private static final String VPX_DECODER_NAME = "OMX.google.vp8.decoder"; 45 private static final String VPX_ENCODER_NAME = "OMX.google.vp8.encoder"; 46 private static final String BASIC_IVF = "video_176x144_vp8_basic.ivf"; 47 private static final long DEFAULT_TIMEOUT_US = 5000; 48 49 private Resources mResources; 50 private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); 51 private ByteBuffer[] mInputBuffers; 52 private ByteBuffer[] mOutputBuffers; 53 54 @Override 55 public void setContext(Context context) { 56 super.setContext(context); 57 mResources = mContext.getResources(); 58 } 59 60 /** 61 * A basic test for VP8 encoder. 62 * 63 * Encodes a raw stream with default configuration options, 64 * and then decodes it to verify the bitstream. 65 */ 66 public void testBasic() throws Exception { 67 encode(BASIC_IVF, 68 R.raw.video_176x144_yv12, 69 176, // width 70 144, // height 71 30); // framerate 72 decode(BASIC_IVF); 73 } 74 75 /** 76 * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored. 77 * 78 * At frame 15, request a sync frame. If one does not occur by EOF the 79 * encoder fails. The test does not verify the output stream. 80 */ 81 public void testSyncFrame() throws Exception { 82 encodeSyncFrame(R.raw.video_176x144_yv12, 83 176, // width 84 144, // height 85 30); // framerate 86 } 87 88 /** 89 * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored. 90 * 91 * Run the sample multiple times. Request periodic changes to the 92 * bitrate and ensure the encoder responds. 93 */ 94 public void testVariableBitrate() throws Exception { 95 encodeVariableBitrate(R.raw.video_176x144_yv12, 96 176, // width 97 144, // height 98 30); // framerate 99 } 100 101 /** 102 * A basic check if an encoded stream is decodable. 103 * 104 * The most basic confirmation we can get about a frame 105 * being properly encoded is trying to decode it. 106 * (Especially in realtime mode encode output is non- 107 * deterministic, therefore a more thorough check like 108 * md5 sum comparison wouldn't work.) 109 * 110 * Indeed, MediaCodec will raise an IllegalStateException 111 * whenever vp8 decoder fails to decode a frame, and 112 * this test uses that fact to verify the bitstream. 113 * 114 * @param filename The name of the IVF file containing encoded bitsream. 115 */ 116 private void decode(String filename) throws Exception { 117 IvfReader ivf = null; 118 try { 119 ivf = new IvfReader(filename); 120 int frameWidth = ivf.getWidth(); 121 int frameHeight = ivf.getHeight(); 122 int frameCount = ivf.getFrameCount(); 123 124 assertTrue(frameWidth > 0); 125 assertTrue(frameHeight > 0); 126 assertTrue(frameCount > 0); 127 128 MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, 129 ivf.getWidth(), 130 ivf.getHeight()); 131 132 Log.d(TAG, "Creating decoder"); 133 MediaCodec decoder = MediaCodec.createByCodecName(VPX_DECODER_NAME); 134 decoder.configure(format, 135 null, // surface 136 null, // crypto 137 0); // flags 138 decoder.start(); 139 140 mInputBuffers = decoder.getInputBuffers(); 141 mOutputBuffers = decoder.getOutputBuffers(); 142 143 // decode loop 144 int frameIndex = 0; 145 boolean sawOutputEOS = false; 146 boolean sawInputEOS = false; 147 148 while (!sawOutputEOS) { 149 if (!sawInputEOS) { 150 int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US); 151 if (inputBufIndex >= 0) { 152 byte[] frame = ivf.readFrame(frameIndex); 153 154 if (frameIndex == frameCount - 1) { 155 sawInputEOS = true; 156 } 157 158 mInputBuffers[inputBufIndex].clear(); 159 mInputBuffers[inputBufIndex].put(frame); 160 mInputBuffers[inputBufIndex].rewind(); 161 162 Log.d(TAG, "Decoding frame at index " + frameIndex); 163 try { 164 decoder.queueInputBuffer( 165 inputBufIndex, 166 0, // offset 167 frame.length, 168 frameIndex, 169 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 170 } catch (IllegalStateException ise) { 171 //That is all what is passed from MediaCodec in case of 172 //decode failure. 173 fail("Failed to decode frame at index " + frameIndex); 174 } 175 frameIndex++; 176 } 177 } 178 179 int result = decoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US); 180 if (result >= 0) { 181 int outputBufIndex = result; 182 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 183 sawOutputEOS = true; 184 } 185 decoder.releaseOutputBuffer(outputBufIndex, false); 186 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 187 mOutputBuffers = decoder.getOutputBuffers(); 188 } 189 } 190 decoder.stop(); 191 decoder.release(); 192 } finally { 193 if (ivf != null) { 194 ivf.close(); 195 } 196 } 197 } 198 199 /** 200 * A basic vp8 encode loop. 201 * 202 * MediaCodec will raise an IllegalStateException 203 * whenever vp8 encoder fails to encode a frame. 204 * 205 * In addition to that written IVF file can be tested 206 * to be decodable in order to verify the bitstream produced. 207 * 208 * Color format of input file should be YUV420, and frameWidth, 209 * frameHeight should be supplied correctly as raw input file doesn't 210 * include any header data. 211 * 212 * @param outputFilename The name of the IVF file to write encoded bitsream 213 * @param rawInputFd File descriptor for the raw input file (YUV420) 214 * @param frameWidth Frame width of input file 215 * @param frameHeight Frame height of input file 216 * @param frameRate Frame rate of input file in frames per second 217 */ 218 private void encode(String outputFilename, int rawInputFd, 219 int frameWidth, int frameHeight, int frameRate) throws Exception { 220 // Create a media format signifying desired output 221 MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight); 222 format.setInteger(MediaFormat.KEY_BIT_RATE, 100000); 223 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 224 CodecCapabilities.COLOR_FormatYUV420Planar); 225 format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); 226 227 Log.d(TAG, "Creating encoder"); 228 MediaCodec encoder; 229 encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME); 230 encoder.configure(format, 231 null, // surface 232 null, // crypto 233 MediaCodec.CONFIGURE_FLAG_ENCODE); 234 encoder.start(); 235 236 mInputBuffers = encoder.getInputBuffers(); 237 mOutputBuffers = encoder.getOutputBuffers(); 238 239 InputStream rawStream = null; 240 IvfWriter ivf = null; 241 242 try { 243 rawStream = mResources.openRawResource(rawInputFd); 244 ivf = new IvfWriter(outputFilename, frameWidth, frameHeight); 245 // encode loop 246 long presentationTimeUs = 0; 247 int inputFrameIndex = 0; 248 int outputFrameIndex = 0; 249 boolean sawInputEOS = false; 250 boolean sawOutputEOS = false; 251 252 while (!sawOutputEOS) { 253 if (!sawInputEOS) { 254 int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US); 255 if (inputBufIndex >= 0) { 256 // YUV420 has 3 planes. Y is full size. U and V are each half size (1/4 the 257 // pixels). 258 int frameSize = frameWidth * frameHeight * 3 / 2; 259 260 byte[] frame = new byte[frameSize]; 261 int bytesRead = rawStream.read(frame); 262 263 if (bytesRead == -1) { 264 sawInputEOS = true; 265 bytesRead = 0; 266 } 267 268 mInputBuffers[inputBufIndex].clear(); 269 mInputBuffers[inputBufIndex].put(frame); 270 mInputBuffers[inputBufIndex].rewind(); 271 272 presentationTimeUs = (inputFrameIndex * 1000000) / frameRate; 273 Log.d(TAG, "Encoding frame at index " + inputFrameIndex); 274 encoder.queueInputBuffer( 275 inputBufIndex, 276 0, // offset 277 bytesRead, // size 278 presentationTimeUs, 279 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 280 281 inputFrameIndex++; 282 } 283 } 284 285 int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US); 286 if (result >= 0) { 287 int outputBufIndex = result; 288 byte[] buffer = new byte[mBufferInfo.size]; 289 mOutputBuffers[outputBufIndex].rewind(); 290 mOutputBuffers[outputBufIndex].get(buffer, 0, mBufferInfo.size); 291 292 if ((outputFrameIndex == 0) 293 && ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0)) { 294 throw new RuntimeException("First frame is not a sync frame."); 295 296 } 297 298 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 299 sawOutputEOS = true; 300 } else { 301 ivf.writeFrame(buffer, mBufferInfo.presentationTimeUs); 302 } 303 encoder.releaseOutputBuffer(outputBufIndex, 304 false); // render 305 306 outputFrameIndex++; 307 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 308 mOutputBuffers = encoder.getOutputBuffers(); 309 } 310 } 311 312 encoder.stop(); 313 encoder.release(); 314 } finally { 315 if (ivf != null) { 316 ivf.close(); 317 } 318 319 if (rawStream != null) { 320 rawStream.close(); 321 } 322 } 323 } 324 325 326 /** 327 * Request Sync Frames 328 * 329 * MediaCodec will raise an IllegalStateException 330 * whenever vp8 encoder fails to encode a frame. 331 * 332 * This presumes a file with 28 frames. Under normal circumstances there 333 * would only be one sync frame: the first one. This test will request an 334 * additional sync frame at 15 and ensure that it occurs by EOF. 335 * 336 * Color format of input file should be YUV420, and frameWidth, 337 * frameHeight should be supplied correctly as raw input file doesn't 338 * include any header data. 339 * 340 * @param rawInputFd File descriptor for the raw input file (YUV420) 341 * @param frameWidth Frame width of input file 342 * @param frameHeight Frame height of input file 343 * @param frameRate Frame rate of input file in frames per second 344 */ 345 private void encodeSyncFrame(int rawInputFd, int frameWidth, 346 int frameHeight, int frameRate) throws Exception { 347 // Create a media format signifying desired output 348 MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight); 349 format.setInteger(MediaFormat.KEY_BIT_RATE, 100000); 350 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 351 CodecCapabilities.COLOR_FormatYUV420Planar); 352 format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); 353 354 Log.d(TAG, "Creating encoder"); 355 MediaCodec encoder; 356 encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME); 357 encoder.configure(format, 358 null, // surface 359 null, // crypto 360 MediaCodec.CONFIGURE_FLAG_ENCODE); 361 encoder.start(); 362 363 mInputBuffers = encoder.getInputBuffers(); 364 mOutputBuffers = encoder.getOutputBuffers(); 365 366 InputStream rawStream = null; 367 368 try { 369 rawStream = mResources.openRawResource(rawInputFd); 370 // encode loop 371 long presentationTimeUs = 0; 372 int inputFrameIndex = 0; 373 boolean sawInputEOS = false; 374 boolean sawOutputEOS = false; 375 boolean syncFrameRequested = false; 376 boolean matchedSyncFrame = false; 377 378 while (!sawOutputEOS) { 379 if (!sawInputEOS) { 380 int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US); 381 if (inputBufIndex >= 0) { 382 int frameSize = frameWidth * frameHeight * 3 / 2; 383 384 byte[] frame = new byte[frameSize]; 385 int bytesRead = rawStream.read(frame); 386 387 if (bytesRead == -1) { 388 sawInputEOS = true; 389 bytesRead = 0; 390 } 391 392 mInputBuffers[inputBufIndex].clear(); 393 mInputBuffers[inputBufIndex].put(frame); 394 mInputBuffers[inputBufIndex].rewind(); 395 396 if (inputFrameIndex == 15) { 397 Log.d(TAG, "Requesting sync frame at index " + inputFrameIndex); 398 Bundle syncFrame = new Bundle(); 399 syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); 400 encoder.setParameters(syncFrame); 401 syncFrameRequested = true; 402 } 403 404 presentationTimeUs = (inputFrameIndex * 1000000) / frameRate; 405 encoder.queueInputBuffer( 406 inputBufIndex, 407 0, // offset 408 bytesRead, // size 409 presentationTimeUs, 410 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 411 412 inputFrameIndex++; 413 } 414 } 415 416 int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US); 417 if (result >= 0) { 418 if (syncFrameRequested && ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0)) { 419 Log.d(TAG, "Found sync frame"); 420 matchedSyncFrame = true; 421 } 422 423 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 424 sawOutputEOS = true; 425 } 426 427 encoder.releaseOutputBuffer(result, 428 false); // render 429 430 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 431 mOutputBuffers = encoder.getOutputBuffers(); 432 } 433 } 434 435 if (!matchedSyncFrame) { 436 throw new RuntimeException("Requested sync frame did not occur"); 437 } 438 439 encoder.stop(); 440 encoder.release(); 441 } finally { 442 if (rawStream != null) { 443 rawStream.close(); 444 } 445 } 446 } 447 448 449 /** 450 * Adjust bitrate 451 * 452 * MediaCodec will raise an IllegalStateException 453 * whenever vp8 encoder fails to encode a frame. 454 * 455 * Encode the file three times: once at the initial bitrate, once at an 456 * increased bitrate, and once at a decreased bitrate. Record the frame 457 * sizes that are returned and verify a strict ordering. 458 * 459 * Color format of input file should be YUV420, and frameWidth, 460 * frameHeight should be supplied correctly as raw input file doesn't 461 * include any header data. 462 * 463 * @param rawInputFd File descriptor for the raw input file (YUV420) 464 * @param frameWidth Frame width of input file 465 * @param frameHeight Frame height of input file 466 * @param frameRate Frame rate of input file in frames per second 467 */ 468 private void encodeVariableBitrate(int rawInputFd, int frameWidth, 469 int frameHeight, int frameRate) throws Exception { 470 // Create a media format signifying desired output 471 MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight); 472 format.setInteger(MediaFormat.KEY_BIT_RATE, 75000); 473 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 474 CodecCapabilities.COLOR_FormatYUV420Planar); 475 format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); 476 477 Log.d(TAG, "Creating encoder"); 478 MediaCodec encoder; 479 encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME); 480 encoder.configure(format, 481 null, // surface 482 null, // crypto 483 MediaCodec.CONFIGURE_FLAG_ENCODE); 484 encoder.start(); 485 486 mInputBuffers = encoder.getInputBuffers(); 487 mOutputBuffers = encoder.getOutputBuffers(); 488 489 InputStream rawStream = null; 490 491 int iteration = 0; 492 int[] bits = new int[100]; 493 494 try { 495 rawStream = mResources.openRawResource(rawInputFd); 496 /* Doc says this is not the default: 497 * http://developer.android.com/reference/java/io/InputStream.html#markSupported() 498 * but it returns true so using .reset() instead of close/open 499 */ 500 if (rawStream.markSupported()) Log.d(TAG, "Stream marking supported"); 501 rawStream.mark(1000000); 502 503 // encode loop 504 long presentationTimeUs = 0; 505 int inputFrameIndex = 0; 506 int outputFrameIndex = 0; 507 boolean sawInputEOS = false; 508 boolean sawOutputEOS = false; 509 510 while (!sawOutputEOS) { 511 if (!sawInputEOS) { 512 int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US); 513 if (inputBufIndex >= 0) { 514 int frameSize = frameWidth * frameHeight * 3 / 2; 515 516 byte[] frame = new byte[frameSize]; 517 int bytesRead = rawStream.read(frame); 518 519 if (bytesRead == -1) { 520 if (iteration < 2) { 521 rawStream.reset(); 522 Bundle bitrate = new Bundle(); 523 if (iteration == 0) { 524 bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 150000); 525 Log.d(TAG, "Setting bitrate to 150000"); 526 } else { 527 bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 25000); 528 Log.d(TAG, "Setting bitrate to 25000"); 529 } 530 encoder.setParameters(bitrate); 531 532 iteration++; 533 continue; 534 } else { 535 sawInputEOS = true; 536 bytesRead = 0; 537 } 538 } 539 540 mInputBuffers[inputBufIndex].clear(); 541 mInputBuffers[inputBufIndex].put(frame); 542 mInputBuffers[inputBufIndex].rewind(); 543 544 presentationTimeUs = (inputFrameIndex * 1000000) / frameRate; 545 encoder.queueInputBuffer( 546 inputBufIndex, 547 0, // offset 548 bytesRead, // size 549 presentationTimeUs, 550 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 551 552 inputFrameIndex++; 553 } 554 } 555 556 int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US); 557 if (result >= 0) { 558 559 bits[outputFrameIndex] = mBufferInfo.size; 560 561 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 562 sawOutputEOS = true; 563 } 564 565 encoder.releaseOutputBuffer(result, 566 false); // render 567 568 outputFrameIndex++; 569 570 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 571 mOutputBuffers = encoder.getOutputBuffers(); 572 } 573 } 574 575 // 29 frames per run 576 int i; 577 int sum = 0; 578 int frames = 29; 579 for(i = 0; i < frames; i++) 580 sum += bits[i]; 581 int midBitrateAvg = sum / frames; 582 583 sum = 0; 584 for(; i < frames * 2; i++) 585 sum += bits[i]; 586 int highBitrateAvg = sum / frames; 587 588 sum = 0; 589 for(; i < frames * 3; i++) 590 sum += bits[i]; 591 int lowBitrateAvg = sum / frames; 592 593 // For the given bitrates we expect mid ~= 350, high ~= 575 and low ~= 150 594 // bytes per frame 595 if ((midBitrateAvg + 100) > highBitrateAvg) 596 throw new RuntimeException("Bitrate did not increase when requesting higher bitrate"); 597 if ((lowBitrateAvg + 100) > midBitrateAvg) 598 throw new RuntimeException("Bitrate did not decrease when requesting lower bitrate"); 599 600 601 encoder.stop(); 602 encoder.release(); 603 } finally { 604 if (rawStream != null) { 605 rawStream.close(); 606 } 607 } 608 } 609 } 610