GMPUtils.cpp (11181B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "GMPUtils.h" 8 9 #include "GMPLog.h" 10 #include "GMPService.h" 11 #include "VideoLimits.h" 12 #include "gmp-video-frame-encoded.h" 13 #include "mozIGeckoMediaPluginService.h" 14 #include "mozilla/Base64.h" 15 #include "mozilla/EndianUtils.h" 16 #include "nsCOMPtr.h" 17 #include "nsCRTGlue.h" 18 #include "nsDirectoryServiceDefs.h" 19 #include "nsIConsoleService.h" 20 #include "nsIFile.h" 21 #include "nsLiteralString.h" 22 #include "prio.h" 23 24 namespace mozilla { 25 26 void SplitAt(const char* aDelims, const nsACString& aInput, 27 nsTArray<nsCString>& aOutTokens) { 28 nsAutoCString str(aInput); 29 char* end = str.BeginWriting(); 30 const char* start = nullptr; 31 while (!!(start = NS_strtok(aDelims, &end))) { 32 aOutTokens.AppendElement(nsCString(start)); 33 } 34 } 35 36 nsCString ToHexString(const uint8_t* aBytes, uint32_t aLength) { 37 static const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', 38 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 39 nsCString str; 40 for (uint32_t i = 0; i < aLength; i++) { 41 char buf[3]; 42 buf[0] = hex[(aBytes[i] & 0xf0) >> 4]; 43 buf[1] = hex[aBytes[i] & 0x0f]; 44 buf[2] = 0; 45 str.AppendASCII(buf); 46 } 47 return str; 48 } 49 50 nsCString ToHexString(const nsTArray<uint8_t>& aBytes) { 51 return ToHexString(aBytes.Elements(), aBytes.Length()); 52 } 53 54 bool FileExists(nsIFile* aFile) { 55 bool exists = false; 56 return aFile && NS_SUCCEEDED(aFile->Exists(&exists)) && exists; 57 } 58 59 DirectoryEnumerator::DirectoryEnumerator(nsIFile* aPath, Mode aMode) 60 : mMode(aMode) { 61 aPath->GetDirectoryEntries(getter_AddRefs(mIter)); 62 } 63 64 already_AddRefed<nsIFile> DirectoryEnumerator::Next() { 65 if (!mIter) { 66 return nullptr; 67 } 68 bool hasMore = false; 69 while (NS_SUCCEEDED(mIter->HasMoreElements(&hasMore)) && hasMore) { 70 nsCOMPtr<nsISupports> supports; 71 nsresult rv = mIter->GetNext(getter_AddRefs(supports)); 72 if (NS_FAILED(rv)) { 73 continue; 74 } 75 76 nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv)); 77 if (NS_FAILED(rv)) { 78 continue; 79 } 80 81 if (mMode == DirsOnly) { 82 bool isDirectory = false; 83 rv = path->IsDirectory(&isDirectory); 84 if (NS_FAILED(rv) || !isDirectory) { 85 continue; 86 } 87 } 88 return path.forget(); 89 } 90 return nullptr; 91 } 92 93 bool ReadIntoArray(nsIFile* aFile, nsTArray<uint8_t>& aOutDst, 94 size_t aMaxLength) { 95 if (!FileExists(aFile)) { 96 return false; 97 } 98 99 PRFileDesc* fd = nullptr; 100 nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); 101 if (NS_WARN_IF(NS_FAILED(rv))) { 102 return false; 103 } 104 105 int32_t length = PR_Seek(fd, 0, PR_SEEK_END); 106 PR_Seek(fd, 0, PR_SEEK_SET); 107 108 if (length < 0 || (size_t)length > aMaxLength) { 109 NS_WARNING("EME file is longer than maximum allowed length"); 110 PR_Close(fd); 111 return false; 112 } 113 aOutDst.SetLength(length); 114 int32_t bytesRead = PR_Read(fd, aOutDst.Elements(), length); 115 PR_Close(fd); 116 return (bytesRead == length); 117 } 118 119 bool ReadIntoString(nsIFile* aFile, nsCString& aOutDst, size_t aMaxLength) { 120 nsTArray<uint8_t> buf; 121 bool rv = ReadIntoArray(aFile, buf, aMaxLength); 122 if (rv) { 123 buf.AppendElement(0); // Append null terminator, required by nsC*String. 124 aOutDst = nsDependentCString((const char*)buf.Elements(), buf.Length() - 1); 125 } 126 return rv; 127 } 128 129 bool GMPInfoFileParser::Init(nsIFile* aInfoFile) { 130 nsTArray<nsCString> lines; 131 static const size_t MAX_GMP_INFO_FILE_LENGTH = 5 * 1024; 132 133 nsAutoCString info; 134 if (!ReadIntoString(aInfoFile, info, MAX_GMP_INFO_FILE_LENGTH)) { 135 NS_WARNING("Failed to read info file in GMP process."); 136 return false; 137 } 138 139 // Note: we pass "\r\n" to SplitAt so that we'll split lines delimited 140 // by \n (Unix), \r\n (Windows) and \r (old MacOSX). 141 SplitAt("\r\n", info, lines); 142 143 for (nsCString line : lines) { 144 // Field name is the string up to but not including the first ':' 145 // character on the line. 146 int32_t colon = line.FindChar(':'); 147 if (colon <= 0) { 148 // Not allowed to be the first character. 149 // Info field name must be at least one character. 150 continue; 151 } 152 nsAutoCString key(Substring(line, 0, colon)); 153 ToLowerCase(key); 154 key.Trim(" "); 155 156 auto value = MakeUnique<nsCString>(Substring(line, colon + 1)); 157 value->Trim(" "); 158 mValues.InsertOrUpdate( 159 key, 160 std::move(value)); // Hashtable assumes ownership of value. 161 } 162 163 return true; 164 } 165 166 bool GMPInfoFileParser::Contains(const nsCString& aKey) const { 167 nsCString key(aKey); 168 ToLowerCase(key); 169 return mValues.Contains(key); 170 } 171 172 nsCString GMPInfoFileParser::Get(const nsCString& aKey) const { 173 MOZ_ASSERT(Contains(aKey)); 174 nsCString key(aKey); 175 ToLowerCase(key); 176 nsCString* p = nullptr; 177 if (mValues.Get(key, &p)) { 178 return nsCString(*p); 179 } 180 return ""_ns; 181 } 182 183 bool HaveGMPFor(const nsACString& aAPI, const nsTArray<nsCString>& aTags) { 184 nsCOMPtr<mozIGeckoMediaPluginService> mps = 185 do_GetService("@mozilla.org/gecko-media-plugin-service;1"); 186 if (NS_WARN_IF(!mps)) { 187 return false; 188 } 189 190 bool hasPlugin = false; 191 if (NS_FAILED(mps->HasPluginForAPI(aAPI, aTags, &hasPlugin))) { 192 return false; 193 } 194 return hasPlugin; 195 } 196 197 bool IsOnGMPThread() { 198 nsCOMPtr<mozIGeckoMediaPluginService> mps = 199 do_GetService("@mozilla.org/gecko-media-plugin-service;1"); 200 MOZ_ASSERT(mps); 201 202 nsCOMPtr<nsIThread> gmpThread; 203 nsresult rv = mps->GetThread(getter_AddRefs(gmpThread)); 204 MOZ_ASSERT(gmpThread); 205 return NS_SUCCEEDED(rv) && gmpThread && gmpThread->IsOnCurrentThread(); 206 } 207 208 void LogToConsole(const nsAString& aMsg) { 209 nsCOMPtr<nsIConsoleService> console( 210 do_GetService("@mozilla.org/consoleservice;1")); 211 if (!console) { 212 NS_WARNING("Failed to log message to console."); 213 return; 214 } 215 nsAutoString msg(aMsg); 216 console->LogStringMessage(msg.get()); 217 } 218 219 already_AddRefed<nsISerialEventTarget> GetGMPThread() { 220 RefPtr<gmp::GeckoMediaPluginService> service = 221 gmp::GeckoMediaPluginService::GetGeckoMediaPluginService(); 222 nsCOMPtr<nsISerialEventTarget> thread = 223 service ? service->GetGMPThread() : nullptr; 224 return thread.forget(); 225 } 226 227 static size_t Align16(size_t aNumber) { 228 const size_t mask = 15; // Alignment - 1. 229 return (aNumber + mask) & ~mask; 230 } 231 232 size_t I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight) { 233 if (aWidth <= 0 || aHeight <= 0 || aWidth > MAX_VIDEO_WIDTH || 234 aHeight > MAX_VIDEO_HEIGHT) { 235 return 0; 236 } 237 238 size_t ySize = Align16(aWidth) * Align16(aHeight); 239 return ySize + (ySize / 4) * 2; 240 } 241 242 static int SizeNumBytes(GMPBufferType aBufferType) { 243 switch (aBufferType) { 244 case GMP_BufferSingle: 245 return 0; 246 case GMP_BufferLength8: 247 return 1; 248 case GMP_BufferLength16: 249 return 2; 250 case GMP_BufferLength24: 251 return 3; 252 case GMP_BufferLength32: 253 return 4; 254 default: 255 MOZ_CRASH("Unexpected buffer type"); 256 } 257 } 258 259 bool AdjustOpenH264NALUSequence(GMPVideoEncodedFrame* aEncodedFrame) { 260 MOZ_ASSERT(aEncodedFrame); 261 MOZ_ASSERT(IsOnGMPThread()); 262 263 uint8_t* encodedBuffer = aEncodedFrame->Buffer(); 264 uint32_t encodedSize = aEncodedFrame->Size(); 265 GMPBufferType encodedType = aEncodedFrame->BufferType(); 266 267 if (NS_WARN_IF(!encodedBuffer)) { 268 GMP_LOG_ERROR("GMP plugin returned null buffer"); 269 return false; 270 } 271 272 // Libwebrtc's RtpPacketizerH264 expects a 3- or 4-byte NALU start sequence 273 // before the start of the NALU payload. {0,0,1} or {0,0,0,1}. We set this 274 // in-place. Any other length of the length field we reject. 275 276 const int sizeNumBytes = SizeNumBytes(encodedType); 277 uint32_t unitOffset = 0; 278 uint32_t unitSize = 0; 279 // Make sure we don't read past the end of the buffer getting the size 280 while (unitOffset + sizeNumBytes < encodedSize) { 281 uint8_t* unitBuffer = encodedBuffer + unitOffset; 282 switch (encodedType) { 283 case GMP_BufferLength24: { 284 #if MOZ_LITTLE_ENDIAN() 285 unitSize = (static_cast<uint32_t>(*unitBuffer)) | 286 (static_cast<uint32_t>(*(unitBuffer + 1)) << 8) | 287 (static_cast<uint32_t>(*(unitBuffer + 2)) << 16); 288 #else 289 unitSize = (static_cast<uint32_t>(*unitBuffer) << 16) | 290 (static_cast<uint32_t>(*(unitBuffer + 1)) << 8) | 291 (static_cast<uint32_t>(*(unitBuffer + 2))); 292 #endif 293 const uint8_t startSequence[] = {0, 0, 1}; 294 if (memcmp(unitBuffer, startSequence, 3) == 0) { 295 // This is a bug in OpenH264 where it misses to convert the NALU start 296 // sequence to the NALU size per the GMP protocol. We mitigate this by 297 // letting it through as this is what libwebrtc already expects and 298 // scans for. 299 unitSize = encodedSize - 3; 300 break; 301 } 302 memcpy(unitBuffer, startSequence, 3); 303 break; 304 } 305 case GMP_BufferLength32: { 306 #if MOZ_LITTLE_ENDIAN() 307 unitSize = LittleEndian::readUint32(unitBuffer); 308 #else 309 unitSize = BigEndian::readUint32(unitBuffer); 310 #endif 311 const uint8_t startSequence[] = {0, 0, 0, 1}; 312 if (memcmp(unitBuffer, startSequence, 4) == 0) { 313 // This is a bug in OpenH264 where it misses to convert the NALU start 314 // sequence to the NALU size per the GMP protocol. We mitigate this by 315 // letting it through as this is what libwebrtc already expects and 316 // scans for. 317 unitSize = encodedSize - 4; 318 break; 319 } 320 memcpy(unitBuffer, startSequence, 4); 321 break; 322 } 323 default: 324 GMP_LOG_ERROR("GMP plugin returned type we cannot handle (%d)", 325 encodedType); 326 return false; 327 } 328 329 MOZ_ASSERT(unitSize != 0); 330 MOZ_ASSERT(unitOffset + sizeNumBytes + unitSize <= encodedSize); 331 if (unitSize == 0 || unitOffset + sizeNumBytes + unitSize > encodedSize) { 332 // XXX Should we kill the plugin for returning extra bytes? Probably 333 GMP_LOG_ERROR( 334 "GMP plugin returned badly formatted encoded data: " 335 "unitOffset=%u, sizeNumBytes=%d, unitSize=%u, size=%u", 336 unitOffset, sizeNumBytes, unitSize, encodedSize); 337 return false; 338 } 339 340 unitOffset += sizeNumBytes + unitSize; 341 } 342 343 if (unitOffset != encodedSize) { 344 // At most 3 bytes can be left over, depending on buffertype 345 GMP_LOG_DEBUG("GMP plugin returned %u extra bytes", 346 encodedSize - unitOffset); 347 } 348 349 return true; 350 } 351 352 MediaResult ToMediaResult(GMPErr aErr, const nsACString& aMessage) { 353 nsPrintfCString msg("%s (GMPErr:%x)", aMessage.Data(), aErr); 354 switch (aErr) { 355 case GMPDecodeErr: 356 return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, msg); 357 case GMPNotImplementedErr: 358 return MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, msg); 359 case GMPAbortedErr: 360 return MediaResult(NS_ERROR_DOM_MEDIA_ABORT_ERR, msg); 361 default: 362 return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, msg); 363 } 364 } 365 366 } // namespace mozilla