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