1 /*
2  * Copyright (C) 2020 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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "AVCUtilsUnitTest"
19 #include <utils/Log.h>
20 
21 #include <fstream>
22 #include <memory>
23 
24 #include "media/stagefright/foundation/ABitReader.h"
25 #include "media/stagefright/foundation/avc_utils.h"
26 
27 #include "AVCUtilsTestEnvironment.h"
28 
29 constexpr size_t kSmallBufferSize = 2;
30 constexpr uint8_t kSPSmask = 0x1f;
31 constexpr uint8_t kSPSStartCode = 0x07;
32 constexpr uint8_t kConfigVersion = 0x01;
33 
34 using namespace android;
35 
36 static AVCUtilsTestEnvironment *gEnv = nullptr;
37 
38 class MpegAudioUnitTest
39     : public ::testing::TestWithParam<
40               tuple</*audioHeader*/ uint32_t, /*frameSize*/ int32_t, /*sampleRate*/ int32_t,
41                     /*numChannels*/ int32_t, /*bitRate*/ int32_t, /*numSamples*/ int32_t>> {};
42 
43 class VOLDimensionTest
44     : public ::testing::TestWithParam<
45               tuple</*fileName*/ string, /*volWidth*/ int32_t, /*volHeight*/ int32_t>> {};
46 
47 class AVCUtils {
48   public:
SetUpAVCUtils(string fileName,string infoFileName)49     bool SetUpAVCUtils(string fileName, string infoFileName) {
50         mInputFile = gEnv->getRes() + fileName;
51         mInputFileStream.open(mInputFile, ifstream::in);
52         if (!mInputFileStream.is_open()) return false;
53 
54         mInfoFile = gEnv->getRes() + infoFileName;
55         mInfoFileStream.open(mInfoFile, ifstream::in);
56         if (!mInputFileStream.is_open()) return false;
57         return true;
58     }
59 
~AVCUtils()60     ~AVCUtils() {
61         if (mInputFileStream.is_open()) mInputFileStream.close();
62         if (mInfoFileStream.is_open()) mInfoFileStream.close();
63     }
64 
65     string mInputFile;
66     string mInfoFile;
67 
68     ifstream mInputFileStream;
69     ifstream mInfoFileStream;
70 };
71 
72 class AVCDimensionTest
73     : public AVCUtils,
74       public ::testing::TestWithParam<
75               tuple</*fileName*/ string, /*infoFileName*/ string,
76                     /*avcWidth*/ size_t, /*avcHeight*/ size_t, /*numberOfNALUnits*/ int32_t>> {
77   public:
SetUp()78     virtual void SetUp() override {
79         tuple<string, string, size_t, size_t, size_t> params = GetParam();
80         string fileName = get<0>(params);
81         string infoFileName = get<1>(params);
82         AVCUtils::SetUpAVCUtils(fileName, infoFileName);
83 
84         mFrameWidth = get<2>(params);
85         mFrameHeight = get<3>(params);
86         mNalUnitsExpected = get<4>(params);
87     }
88 
89     size_t mFrameWidth;
90     size_t mFrameHeight;
91     int32_t mNalUnitsExpected;
92 };
93 
94 class AvccBoxTest : public AVCDimensionTest {
95   public:
SetUp()96     virtual void SetUp() override { AVCDimensionTest::SetUp(); }
97 };
98 
99 class AVCFrameTest
100     : public AVCUtils,
101       public ::testing::TestWithParam<pair</*fileName*/ string, /*infoFileName*/ string>> {
102   public:
SetUp()103     virtual void SetUp() override {
104         string fileName = GetParam().first;
105         string infoFileName = GetParam().second;
106         AVCUtils::SetUpAVCUtils(fileName, infoFileName);
107     }
108 };
109 
TEST_P(MpegAudioUnitTest,AudioProfileTest)110 TEST_P(MpegAudioUnitTest, AudioProfileTest) {
111     tuple<uint32_t, size_t, int, int, int, int> params = GetParam();
112     uint32_t header = get<0>(params);
113 
114     size_t audioFrameSize = get<1>(params);
115     int audioSampleRate = get<2>(params);
116     int audioNumChannels = get<3>(params);
117     int audioBitRate = get<4>(params);
118     int audioNumSamples = get<5>(params);
119 
120     size_t frameSize = 0;
121     int sampleRate = 0;
122     int numChannels = 0;
123     int bitRate = 0;
124     int numSamples = 0;
125 
126     bool status = GetMPEGAudioFrameSize(header, &frameSize, &sampleRate, &numChannels, &bitRate,
127                                         &numSamples);
128     ASSERT_TRUE(status) << "Failed to get Audio properties";
129 
130     ASSERT_EQ(frameSize, audioFrameSize) << "Wrong frame size found";
131 
132     ASSERT_EQ(sampleRate, audioSampleRate) << "Wrong sample rate found";
133 
134     ASSERT_EQ(numChannels, audioNumChannels) << "Wrong number of channels found";
135 
136     ASSERT_EQ(bitRate, audioBitRate) << "Wrong bit rate found";
137 
138     ASSERT_EQ(numSamples, audioNumSamples) << "Wrong number of samples found";
139 }
140 
TEST_P(VOLDimensionTest,DimensionTest)141 TEST_P(VOLDimensionTest, DimensionTest) {
142     tuple<string, int32_t, int32_t> params = GetParam();
143     string inputFile = gEnv->getRes() + get<0>(params);
144     ifstream inputFileStream;
145     inputFileStream.open(inputFile, ifstream::in);
146     ASSERT_TRUE(inputFileStream.is_open()) << "Failed to open: " << inputFile;
147 
148     struct stat buf;
149     int8_t err = stat(inputFile.c_str(), &buf);
150     ASSERT_EQ(err, 0) << "Failed to get information for file: " << inputFile;
151 
152     size_t fileSize = buf.st_size;
153     ASSERT_NE(fileSize, 0) << "Invalid file size found";
154 
155     std::unique_ptr<uint8_t[]> volBuffer(new uint8_t[fileSize]);
156     ASSERT_NE(volBuffer, nullptr) << "Failed to allocate VOL buffer of size: " << fileSize;
157 
158     inputFileStream.read((char *)(volBuffer.get()), fileSize);
159     ASSERT_EQ(inputFileStream.gcount(), fileSize)
160             << "Failed to read complete file, bytes read: " << inputFileStream.gcount();
161 
162     int32_t width = get<1>(params);
163     int32_t height = get<2>(params);
164     int32_t volWidth = -1;
165     int32_t volHeight = -1;
166 
167     bool status = ExtractDimensionsFromVOLHeader(volBuffer.get(), fileSize, &volWidth, &volHeight);
168     ASSERT_TRUE(status)
169             << "Failed to get VOL dimensions from function: ExtractDimensionsFromVOLHeader()";
170 
171     ASSERT_EQ(volWidth, width) << "Expected width: " << width << "Found: " << volWidth;
172 
173     ASSERT_EQ(volHeight, height) << "Expected height: " << height << "Found: " << volHeight;
174 }
175 
TEST_P(AVCDimensionTest,DimensionTest)176 TEST_P(AVCDimensionTest, DimensionTest) {
177     int32_t numNalUnits = 0;
178     int32_t avcWidth = -1;
179     int32_t avcHeight = -1;
180     string line;
181     string type;
182     size_t chunkLength;
183     while (getline(mInfoFileStream, line)) {
184         istringstream stringLine(line);
185         stringLine >> type >> chunkLength;
186         ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
187 
188         std::unique_ptr<uint8_t[]> dataArray(new uint8_t[chunkLength]);
189         const uint8_t *data = dataArray.get();
190         ASSERT_NE(data, nullptr) << "Failed to create a data buffer of size: " << chunkLength;
191 
192         const uint8_t *nalStart;
193         size_t nalSize;
194 
195         mInputFileStream.read((char *)data, chunkLength);
196         ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
197                 << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
198 
199         size_t smallBufferSize = kSmallBufferSize;
200         uint8_t sanityDataBuffer[smallBufferSize];
201         const uint8_t *sanityData = sanityDataBuffer;
202         memcpy((void *)sanityData, (void *)data, smallBufferSize);
203 
204         // sanityData could be changed, but sanityDataPtr is not and can be cleaned up.
205         status_t result = getNextNALUnit(&sanityData, &smallBufferSize, &nalStart, &nalSize, true);
206         ASSERT_EQ(result, -EAGAIN) << "Invalid result found when wrong NAL unit passed";
207 
208         while (!getNextNALUnit(&data, &chunkLength, &nalStart, &nalSize, true)) {
209             numNalUnits++;
210             // Check if it's an SPS
211             if ((nalStart[0] & kSPSmask) != kSPSStartCode) continue;
212             ASSERT_TRUE(nalSize > 0) << "NAL unit size must be greater than 0";
213 
214             sp<ABuffer> spsBuffer = new ABuffer(nalSize);
215             ASSERT_NE(spsBuffer, nullptr) << "ABuffer returned null for size: " << nalSize;
216 
217             memcpy(spsBuffer->data(), nalStart, nalSize);
218             FindAVCDimensions(spsBuffer, &avcWidth, &avcHeight);
219             spsBuffer.clear();
220             ASSERT_EQ(avcWidth, mFrameWidth)
221                     << "Expected width: " << mFrameWidth << "Found: " << avcWidth;
222 
223             ASSERT_EQ(avcHeight, mFrameHeight)
224                     << "Expected height: " << mFrameHeight << "Found: " << avcHeight;
225         }
226     }
227     if (mNalUnitsExpected < 0) {
228         ASSERT_GT(numNalUnits, 0) << "Failed to find an NAL Unit";
229     } else {
230         ASSERT_EQ(numNalUnits, mNalUnitsExpected)
231                 << "Expected number of NAL units: " << mNalUnitsExpected
232                 << " found: " << numNalUnits;
233     }
234 }
235 
TEST_P(AvccBoxTest,AvccBoxValidationTest)236 TEST_P(AvccBoxTest, AvccBoxValidationTest) {
237     int32_t avcWidth = -1;
238     int32_t avcHeight = -1;
239     int32_t accessUnitLength = 0;
240     int32_t profile = -1;
241     int32_t level = -1;
242     string line;
243     string type;
244     size_t chunkLength;
245     while (getline(mInfoFileStream, line)) {
246         istringstream stringLine(line);
247         stringLine >> type >> chunkLength;
248 
249         if (type.compare("SPS") && type.compare("PPS")) continue;
250         ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
251 
252         accessUnitLength += chunkLength;
253 
254         if (!type.compare("SPS")) {
255             std::unique_ptr<uint8_t[]> dataArray(new uint8_t[chunkLength]);
256             const uint8_t *data = dataArray.get();
257             ASSERT_NE(data, nullptr) << "Failed to create a data buffer of size: " << chunkLength;
258 
259             const uint8_t *nalStart;
260             size_t nalSize;
261 
262             mInputFileStream.read((char *)data, (uint32_t)chunkLength);
263             ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
264                     << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
265 
266             while (!getNextNALUnit(&data, &chunkLength, &nalStart, &nalSize, true)) {
267                 // Check if it's an SPS
268                 ASSERT_TRUE(nalSize > 0 && (nalStart[0] & kSPSmask) == kSPSStartCode)
269                         << "Failed to get SPS";
270 
271                 ASSERT_GE(nalSize, 4) << "SPS size must be greater than or equal to 4";
272 
273                 profile = nalStart[1];
274                 level = nalStart[3];
275             }
276         }
277     }
278     std::unique_ptr<uint8_t[]> accessUnitData(new uint8_t[accessUnitLength]);
279     ASSERT_NE(accessUnitData, nullptr) << "Failed to create a buffer of size: " << accessUnitLength;
280 
281     mInputFileStream.seekg(0, ios::beg);
282     mInputFileStream.read((char *)accessUnitData.get(), accessUnitLength);
283     ASSERT_EQ(mInputFileStream.gcount(), accessUnitLength)
284             << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
285 
286     sp<ABuffer> accessUnit = new ABuffer(accessUnitLength);
287     ASSERT_NE(accessUnit, nullptr)
288             << "Failed to create an android data buffer of size: " << accessUnitLength;
289 
290     memcpy(accessUnit->data(), accessUnitData.get(), accessUnitLength);
291     sp<ABuffer> csdDataBuffer = MakeAVCCodecSpecificData(accessUnit, &avcWidth, &avcHeight);
292     ASSERT_NE(csdDataBuffer, nullptr) << "No data returned from MakeAVCCodecSpecificData()";
293 
294     ASSERT_EQ(avcWidth, mFrameWidth) << "Expected width: " << mFrameWidth << "Found: " << avcWidth;
295 
296     ASSERT_EQ(avcHeight, mFrameHeight)
297             << "Expected height: " << mFrameHeight << "Found: " << avcHeight;
298 
299     uint8_t *csdData = csdDataBuffer->data();
300     ASSERT_EQ(*csdData, kConfigVersion) << "Invalid configuration version";
301 
302     ASSERT_GE(csdDataBuffer->size(), 4) << "CSD data size must be greater than or equal to 4";
303 
304     ASSERT_EQ(*(csdData + 1), profile)
305             << "Expected AVC profile: " << profile << " found: " << *(csdData + 1);
306 
307     ASSERT_EQ(*(csdData + 3), level)
308             << "Expected AVC level: " << level << " found: " << *(csdData + 3);
309     csdDataBuffer.clear();
310     accessUnit.clear();
311 }
312 
TEST_P(AVCFrameTest,FrameTest)313 TEST_P(AVCFrameTest, FrameTest) {
314     string line;
315     string type;
316     size_t chunkLength;
317     int32_t frameLayerID;
318     while (getline(mInfoFileStream, line)) {
319         uint32_t layerID = 0;
320         istringstream stringLine(line);
321         stringLine >> type >> chunkLength >> frameLayerID;
322         ASSERT_GT(chunkLength, 0) << "Length of the data chunk must be greater than zero";
323 
324         std::unique_ptr<char[]> data(new char[chunkLength]);
325         ASSERT_NE(data, nullptr) << "Failed to allocation data buffer of size: " << chunkLength;
326 
327         mInputFileStream.read(data.get(), chunkLength);
328         ASSERT_EQ(mInputFileStream.gcount(), chunkLength)
329                 << "Failed to read complete file, bytes read: " << mInputFileStream.gcount();
330 
331         if (!type.compare("IDR")) {
332             bool isIDR = IsIDR((uint8_t *)data.get(), chunkLength);
333             ASSERT_TRUE(isIDR);
334 
335             layerID = FindAVCLayerId((uint8_t *)data.get(), chunkLength);
336             ASSERT_EQ(layerID, frameLayerID) << "Wrong layer ID found";
337         } else if (!type.compare("P") || !type.compare("B")) {
338             sp<ABuffer> accessUnit = new ABuffer(chunkLength);
339             ASSERT_NE(accessUnit, nullptr) << "Unable to create access Unit";
340 
341             memcpy(accessUnit->data(), data.get(), chunkLength);
342             bool isReferenceFrame = IsAVCReferenceFrame(accessUnit);
343             ASSERT_TRUE(isReferenceFrame);
344 
345             accessUnit.clear();
346             layerID = FindAVCLayerId((uint8_t *)data.get(), chunkLength);
347             ASSERT_EQ(layerID, frameLayerID) << "Wrong layer ID found";
348         }
349     }
350 }
351 
352 INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, MpegAudioUnitTest,
353                          ::testing::Values(make_tuple(0xFFFB9204, 418, 44100, 2, 128, 1152),
354                                            make_tuple(0xFFFB7604, 289, 48000, 2, 96, 1152),
355                                            make_tuple(0xFFFE5604, 164, 48000, 2, 160, 384)));
356 
357 // Info File contains the type and length for each chunk/frame
358 INSTANTIATE_TEST_SUITE_P(
359         AVCUtilsTestAll, AVCDimensionTest,
360         ::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
361                                      "crowd_8x8p50f32_200kbps_bp.info", 8, 8, 11),
362                           make_tuple("crowd_640x360p24f300_1000kbps_bp.h264",
363                                      "crowd_640x360p24f300_1000kbps_bp.info", 640, 360, 11),
364                           make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
365                                      "crowd_1280x720p30f300_5000kbps_bp.info", 1280, 720, 12),
366                           make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
367                                      "crowd_1920x1080p50f300_12000kbps_bp.info", 1920, 1080, 14),
368                           make_tuple("crowd_3840x2160p60f300_68000kbps_bp.h264",
369                                      "crowd_3840x2160p60f300_68000kbps_bp.info", 3840, 2160, 14)));
370 
371 // Info File contains the type and length for each chunk/frame
372 INSTANTIATE_TEST_SUITE_P(
373         AVCUtilsTestAll, AvccBoxTest,
374         ::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
375                                      "crowd_8x8p50f32_200kbps_bp.info", 8, 8, 11),
376                           make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
377                                      "crowd_1280x720p30f300_5000kbps_bp.info", 1280, 720, 12),
378                           make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
379                                      "crowd_1920x1080p50f300_12000kbps_bp.info", 1920, 1080, 14)));
380 
381 // Info File contains the type and length for each chunk/frame
382 INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, VOLDimensionTest,
383                          ::testing::Values(make_tuple("volData_720_480", 720, 480),
384                                            make_tuple("volData_1280_720", 1280, 720),
385                                            make_tuple("volData_1920_1080", 1920, 1080)));
386 
387 // Info File contains the type, length and layer ID for each chunk/frame
388 INSTANTIATE_TEST_SUITE_P(AVCUtilsTestAll, AVCFrameTest,
389                          ::testing::Values(make_tuple("crowd_8x8p50f32_200kbps_bp.h264",
390                                                       "crowd_8x8p50f32_200kbps_bp.info"),
391                                            make_tuple("crowd_640x360p24f300_1000kbps_bp.h264",
392                                                       "crowd_640x360p24f300_1000kbps_bp.info"),
393                                            make_tuple("crowd_1280x720p30f300_5000kbps_bp.h264",
394                                                       "crowd_1280x720p30f300_5000kbps_bp.info"),
395                                            make_tuple("crowd_1920x1080p50f300_12000kbps_bp.h264",
396                                                       "crowd_1920x1080p50f300_12000kbps_bp.info"),
397                                            make_tuple("crowd_3840x2160p60f300_68000kbps_bp.h264",
398                                                       "crowd_3840x2160p60f300_68000kbps_bp.info")));
399 
main(int argc,char ** argv)400 int main(int argc, char **argv) {
401     gEnv = new AVCUtilsTestEnvironment();
402     ::testing::AddGlobalTestEnvironment(gEnv);
403     ::testing::InitGoogleTest(&argc, argv);
404     int status = gEnv->initFromOptions(argc, argv);
405     if (status == 0) {
406         status = RUN_ALL_TESTS();
407         ALOGV("Test result = %d\n", status);
408     }
409     return status;
410 }
411