1 /* 2 * Copyright (C) 2022 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.mediav2.common.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 import static org.junit.Assert.fail; 23 24 import android.graphics.ImageFormat; 25 import android.graphics.Rect; 26 import android.media.Image; 27 import android.media.MediaCodec; 28 import android.media.MediaCodecList; 29 import android.media.MediaExtractor; 30 import android.media.MediaFormat; 31 32 import java.io.File; 33 import java.io.FileNotFoundException; 34 import java.io.FileOutputStream; 35 import java.io.IOException; 36 import java.nio.ByteBuffer; 37 import java.util.ArrayList; 38 39 import org.junit.AssumptionViolatedException; 40 41 /** 42 * Class to decode a video track of a clip and write the result to a file. 43 */ 44 public class DecodeStreamToYuv extends CodecDecoderTestBase { 45 private static final String LOG_TAG = DecodeStreamToYuv.class.getSimpleName(); 46 47 private final MediaFormat mStreamFormat; 48 private final ByteBuffer mStreamBuffer; 49 private final ArrayList<MediaCodec.BufferInfo> mStreamBufferInfos; 50 private final int mFrameLimit; 51 private final String mOutputPrefix; 52 53 private String mOutputFile; 54 private int mWidth; 55 private int mHeight; 56 private int mBytesPerSample; 57 DecodeStreamToYuv(String mediaType, String inpFile, int frameLimit, String outYuvPrefix)58 public DecodeStreamToYuv(String mediaType, String inpFile, int frameLimit, String outYuvPrefix) 59 throws IOException { 60 super(findDecoderForStream(mediaType, inpFile), mediaType, inpFile, LOG_TAG); 61 mStreamFormat = null; 62 mStreamBuffer = null; 63 mStreamBufferInfos = null; 64 mFrameLimit = frameLimit; 65 mOutputPrefix = outYuvPrefix; 66 } 67 DecodeStreamToYuv(String mediaType, String inpFile, int frameLimit)68 public DecodeStreamToYuv(String mediaType, String inpFile, int frameLimit) throws IOException { 69 this(mediaType, inpFile, frameLimit, "test" + LOG_TAG); 70 } 71 DecodeStreamToYuv(String mediaType, String inpFile)72 public DecodeStreamToYuv(String mediaType, String inpFile) throws IOException { 73 this(mediaType, inpFile, Integer.MAX_VALUE); 74 } 75 DecodeStreamToYuv(MediaFormat streamFormat, ByteBuffer streamBuffer, ArrayList<MediaCodec.BufferInfo> streamBufferInfos)76 public DecodeStreamToYuv(MediaFormat streamFormat, ByteBuffer streamBuffer, 77 ArrayList<MediaCodec.BufferInfo> streamBufferInfos) { 78 super(findDecoderForFormat(streamFormat), streamFormat.getString(MediaFormat.KEY_MIME), 79 null, LOG_TAG); 80 mStreamFormat = streamFormat; 81 mStreamBuffer = streamBuffer; 82 mStreamBufferInfos = streamBufferInfos; 83 mFrameLimit = Integer.MAX_VALUE; 84 mOutputPrefix = "test" + LOG_TAG; 85 } 86 getDecodedYuv()87 public RawResource getDecodedYuv() { 88 File tmp = null; 89 try { 90 tmp = File.createTempFile(mOutputPrefix, ".yuv"); 91 mOutputFile = tmp.getAbsolutePath(); 92 if (mStreamFormat != null) { 93 decodeToMemory(mStreamBuffer, mStreamBufferInfos, mStreamFormat, mCodecName); 94 } else { 95 decodeToMemory(mTestFile, mCodecName, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC, 96 mFrameLimit); 97 } 98 } catch (AssumptionViolatedException e) { 99 // Make sure we allow AssumptionViolatedExceptions through. 100 throw e; 101 } catch (Exception e) { 102 if (tmp != null && tmp.exists()) assertTrue(tmp.delete()); 103 throw new RuntimeException(e.getMessage()); 104 } catch (AssertionError e) { 105 if (tmp != null && tmp.exists()) assertTrue(tmp.delete()); 106 throw new AssertionError(e.getMessage()); 107 } 108 return new RawResource.Builder() 109 .setFileName(mOutputFile, false) 110 .setDimension(mWidth, mHeight) 111 .setBytesPerSample(mBytesPerSample) 112 .setColorFormat(ImageFormat.UNKNOWN) 113 .build(); 114 } 115 getFormatInStream(String mediaType, String file)116 public static MediaFormat getFormatInStream(String mediaType, String file) throws IOException { 117 File tmp = new File(file); 118 if (!tmp.exists()) { 119 throw new FileNotFoundException("Test Setup Error, missing file: " + file); 120 } 121 MediaExtractor extractor = new MediaExtractor(); 122 extractor.setDataSource(file); 123 MediaFormat format = null; 124 for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) { 125 MediaFormat fmt = extractor.getTrackFormat(trackID); 126 if (mediaType.equalsIgnoreCase(fmt.getString(MediaFormat.KEY_MIME))) { 127 format = fmt; 128 break; 129 } 130 } 131 extractor.release(); 132 if (format == null) { 133 throw new IllegalArgumentException( 134 "No track with mediaType: " + mediaType + " found in file: " + file); 135 } 136 return format; 137 } 138 findDecoderForStream(String mediaType, String file)139 static String findDecoderForStream(String mediaType, String file) throws IOException { 140 return findDecoderForFormat(getFormatInStream(mediaType, file)); 141 } 142 findDecoderForFormat(MediaFormat format)143 static String findDecoderForFormat(MediaFormat format) { 144 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 145 String codecName = mcl.findDecoderForFormat(format); 146 if (codecName == null) { 147 throw new IllegalArgumentException("No decoder for format: " + format); 148 } 149 return codecName; 150 } 151 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)152 protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 153 if (info.size > 0) { 154 Image img = mCodec.getOutputImage(bufferIndex); 155 assertNotNull(img); 156 writeImage(img); 157 if (mOutputCount == 0) { 158 MediaFormat format = mCodec.getOutputFormat(); 159 mWidth = getWidth(format); 160 mHeight = getHeight(format); 161 int imgFormat = img.getFormat(); 162 mBytesPerSample = (ImageFormat.getBitsPerPixel(imgFormat) * 2) / (8 * 3); 163 } 164 mOutputCount++; 165 } 166 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 167 mSawOutputEOS = true; 168 } 169 if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 170 mOutputBuff.saveOutPTS(info.presentationTimeUs); 171 mOutputCount++; 172 } 173 mCodec.releaseOutputBuffer(bufferIndex, false); 174 } 175 getImage(Image image)176 public static YUVImage getImage(Image image) { 177 YUVImage yuvImage = new YUVImage(); 178 int format = image.getFormat(); 179 assertTrue("unexpected image format", 180 format == ImageFormat.YUV_420_888 || format == ImageFormat.YCBCR_P010); 181 int bytesPerSample = (ImageFormat.getBitsPerPixel(format) * 2) / (8 * 3); // YUV420 182 183 Rect cropRect = image.getCropRect(); 184 int imageWidth = cropRect.width(); 185 int imageHeight = cropRect.height(); 186 assertTrue("unexpected image dimensions", imageWidth > 0 && imageHeight > 0); 187 188 int imageLeft = cropRect.left; 189 int imageTop = cropRect.top; 190 Image.Plane[] planes = image.getPlanes(); 191 for (int i = 0; i < planes.length; ++i) { 192 ByteBuffer buf = planes[i].getBuffer(); 193 int width, height, rowStride, pixelStride, x, y, left, top; 194 rowStride = planes[i].getRowStride(); 195 pixelStride = planes[i].getPixelStride(); 196 if (i == 0) { 197 assertEquals(bytesPerSample, pixelStride); 198 width = imageWidth; 199 height = imageHeight; 200 left = imageLeft; 201 top = imageTop; 202 } else { 203 width = imageWidth / 2; 204 height = imageHeight / 2; 205 left = imageLeft / 2; 206 top = imageTop / 2; 207 } 208 int cropOffset = (left * pixelStride) + top * rowStride; 209 // local contiguous pixel buffer 210 byte[] bb = new byte[width * height * bytesPerSample]; 211 212 int base = buf.position(); 213 int pos = base + cropOffset; 214 if (pixelStride == bytesPerSample) { 215 for (y = 0; y < height; ++y) { 216 buf.position(pos + y * rowStride); 217 buf.get(bb, y * width * bytesPerSample, width * bytesPerSample); 218 } 219 } else { 220 // local line buffer 221 byte[] lb = new byte[rowStride]; 222 // do it pixel-by-pixel 223 for (y = 0; y < height; ++y) { 224 buf.position(pos + y * rowStride); 225 // we're only guaranteed to have pixelStride * (width - 1) + 226 // bytesPerSample bytes 227 buf.get(lb, 0, pixelStride * (width - 1) + bytesPerSample); 228 for (x = 0; x < width; ++x) { 229 for (int bytePos = 0; bytePos < bytesPerSample; ++bytePos) { 230 bb[y * width * bytesPerSample + x * bytesPerSample + bytePos] = 231 lb[x * pixelStride + bytePos]; 232 } 233 } 234 } 235 } 236 buf.position(base); 237 yuvImage.mData.add(bb); 238 } 239 return yuvImage; 240 } 241 unWrapYUVImage(YUVImage image)242 public static ArrayList<byte[]> unWrapYUVImage(YUVImage image) { 243 return image.mData; 244 } 245 writeImage(Image image)246 void writeImage(Image image) { 247 YUVImage yuvImage = getImage(image); 248 try (FileOutputStream outputStream = new FileOutputStream(mOutputFile, mOutputCount != 0)) { 249 for (int i = 0; i < yuvImage.mData.size(); i++) { 250 outputStream.write(yuvImage.mData.get(i)); 251 } 252 } catch (IOException e) { 253 fail("unable to write file : " + mOutputFile + " received exception : " + e); 254 } 255 } 256 } 257