• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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