• 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 /**
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