Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/video/capture/win/video_capture_device_factory_win.h" | 5 #include "media/video/capture/win/video_capture_device_factory_win.h" |
| 6 | 6 |
| 7 #include <mfapi.h> | 7 #include <mfapi.h> |
| 8 #include <mferror.h> | 8 #include <mferror.h> |
| 9 | 9 |
| 10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 11 #include "base/lazy_instance.h" | 11 #include "base/lazy_instance.h" |
| 12 #include "base/strings/string_util.h" | 12 #include "base/strings/string_util.h" |
| 13 #include "base/strings/sys_string_conversions.h" | 13 #include "base/strings/sys_string_conversions.h" |
| 14 #include "base/win/metro.h" | 14 #include "base/win/metro.h" |
| 15 #include "base/win/scoped_co_mem.h" | 15 #include "base/win/scoped_co_mem.h" |
| 16 #include "base/win/scoped_variant.h" | 16 #include "base/win/scoped_variant.h" |
| 17 #include "base/win/windows_version.h" | 17 #include "base/win/windows_version.h" |
| 18 #include "media/base/media_switches.h" | 18 #include "media/base/media_switches.h" |
| 19 #include "media/video/capture/win/video_capture_device_mf_win.h" | 19 #include "media/video/capture/win/video_capture_device_mf_win.h" |
| 20 #include "media/video/capture/win/video_capture_device_win.h" | 20 #include "media/video/capture/win/video_capture_device_win.h" |
| 21 | 21 |
| 22 using base::win::ScopedCoMem; | 22 using base::win::ScopedCoMem; |
| 23 using base::win::ScopedComPtr; | 23 using base::win::ScopedComPtr; |
| 24 using base::win::ScopedVariant; | 24 using base::win::ScopedVariant; |
| 25 using Name = media::VideoCaptureDevice::Name; | |
| 26 using Names = media::VideoCaptureDevice::Names; | |
|
mcasas
2014/08/28 11:53:41
Comment: since Name is a nested class inside VCD,
| |
| 25 | 27 |
| 26 namespace media { | 28 namespace media { |
| 27 | 29 |
| 28 // Lazy Instance to initialize the MediaFoundation Library. | 30 // Lazy Instance to initialize the MediaFoundation Library. |
| 29 class MFInitializerSingleton { | 31 class MFInitializerSingleton { |
| 30 public: | 32 public: |
| 31 MFInitializerSingleton() { MFStartup(MF_VERSION, MFSTARTUP_LITE); } | 33 MFInitializerSingleton() { MFStartup(MF_VERSION, MFSTARTUP_LITE); } |
| 32 ~MFInitializerSingleton() { MFShutdown(); } | 34 ~MFInitializerSingleton() { MFShutdown(); } |
| 33 }; | 35 }; |
| 34 | 36 |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 81 | 83 |
| 82 static bool EnumerateVideoDevicesMediaFoundation(IMFActivate*** devices, | 84 static bool EnumerateVideoDevicesMediaFoundation(IMFActivate*** devices, |
| 83 UINT32* count) { | 85 UINT32* count) { |
| 84 ScopedComPtr<IMFAttributes> attributes; | 86 ScopedComPtr<IMFAttributes> attributes; |
| 85 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 1)) | 87 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 1)) |
| 86 return false; | 88 return false; |
| 87 | 89 |
| 88 return SUCCEEDED(MFEnumDeviceSources(attributes, devices, count)); | 90 return SUCCEEDED(MFEnumDeviceSources(attributes, devices, count)); |
| 89 } | 91 } |
| 90 | 92 |
| 91 static void GetDeviceNamesDirectShow(VideoCaptureDevice::Names* device_names) { | 93 static void GetDeviceNamesDirectShow(Names* device_names) { |
| 92 DCHECK(device_names); | 94 DCHECK(device_names); |
| 93 DVLOG(1) << " GetDeviceNamesDirectShow"; | 95 DVLOG(1) << " GetDeviceNamesDirectShow"; |
| 94 | 96 |
| 95 ScopedComPtr<ICreateDevEnum> dev_enum; | 97 ScopedComPtr<ICreateDevEnum> dev_enum; |
| 96 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, | 98 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, |
| 97 CLSCTX_INPROC); | 99 CLSCTX_INPROC); |
| 98 if (FAILED(hr)) | 100 if (FAILED(hr)) |
| 99 return; | 101 return; |
| 100 | 102 |
| 101 ScopedComPtr<IEnumMoniker> enum_moniker; | 103 static const struct{ |
| 102 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, | 104 CLSID class_id; |
| 103 enum_moniker.Receive(), 0); | 105 Name::CaptureApiType capture_api_type; |
| 104 // CreateClassEnumerator returns S_FALSE on some Windows OS | 106 } kDirectShowFilterClasses[] = { |
| 105 // when no camera exist. Therefore the FAILED macro can't be used. | 107 { CLSID_VideoInputDeviceCategory, Name::DIRECT_SHOW }, |
| 106 if (hr != S_OK) | 108 { AM_KSCATEGORY_CROSSBAR, Name::DIRECT_SHOW_WDM} |
| 107 return; | 109 }; |
| 110 | |
| 108 | 111 |
| 109 device_names->clear(); | 112 device_names->clear(); |
| 113 for (int class_index = 0; class_index < arraysize(kDirectShowFilterClasses); | |
| 114 ++class_index) { | |
| 115 ScopedComPtr<IEnumMoniker> enum_moniker; | |
| 116 hr = dev_enum->CreateClassEnumerator( | |
| 117 kDirectShowFilterClasses[class_index].class_id, | |
| 118 enum_moniker.Receive(), | |
| 119 0); | |
| 120 // CreateClassEnumerator returns S_FALSE on some Windows OS | |
| 121 // when no camera exist. Therefore the FAILED macro can't be used. | |
| 122 if (hr != S_OK) | |
| 123 continue; | |
| 110 | 124 |
| 111 // Name of a fake DirectShow filter that exist on computers with | 125 // Name of a fake DirectShow filter that exist on computers with |
| 112 // GTalk installed. | 126 // GTalk installed. |
| 113 static const char kGoogleCameraAdapter[] = "google camera adapter"; | 127 static const char kGoogleCameraAdapter[] = "google camera adapter"; |
| 114 | 128 |
| 115 // Enumerate all video capture devices. | 129 // Enumerate all video capture devices. |
| 116 ScopedComPtr<IMoniker> moniker; | 130 ScopedComPtr<IMoniker> moniker; |
| 117 int index = 0; | 131 int index = 0; |
| 118 while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) { | 132 while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) { |
| 119 ScopedComPtr<IPropertyBag> prop_bag; | 133 ScopedComPtr<IPropertyBag> prop_bag; |
| 120 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); | 134 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, |
| 121 if (FAILED(hr)) { | 135 prop_bag.ReceiveVoid()); |
| 136 if (FAILED(hr)) { | |
| 137 moniker.Release(); | |
| 138 continue; | |
| 139 } | |
| 140 | |
| 141 // Find the description or friendly name. | |
| 142 ScopedVariant name; | |
| 143 hr = prop_bag->Read(L"Description", name.Receive(), 0); | |
| 144 if (FAILED(hr)) | |
| 145 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0); | |
| 146 | |
| 147 if (SUCCEEDED(hr) && name.type() == VT_BSTR) { | |
| 148 // Ignore all VFW drivers and the special Google Camera Adapter. | |
| 149 // Google Camera Adapter is not a real DirectShow camera device. | |
| 150 // VFW are very old Video for Windows drivers that can not be used. | |
| 151 const wchar_t* str_ptr = V_BSTR(&name); | |
| 152 const int name_length = arraysize(kGoogleCameraAdapter) - 1; | |
| 153 | |
| 154 if ((wcsstr(str_ptr, L"(VFW)") == NULL) && | |
| 155 lstrlenW(str_ptr) < name_length || | |
| 156 (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length, | |
| 157 kGoogleCameraAdapter)))) { | |
| 158 std::string id; | |
| 159 std::string device_name(base::SysWideToUTF8(str_ptr)); | |
| 160 name.Reset(); | |
| 161 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0); | |
| 162 if (FAILED(hr) || name.type() != VT_BSTR) { | |
| 163 id = device_name; | |
| 164 } else { | |
| 165 DCHECK_EQ(name.type(), VT_BSTR); | |
| 166 id = base::SysWideToUTF8(V_BSTR(&name)); | |
| 167 } | |
| 168 device_names->push_back(Name(device_name, id, | |
| 169 kDirectShowFilterClasses[class_index].capture_api_type)); | |
| 170 } | |
| 171 } | |
| 122 moniker.Release(); | 172 moniker.Release(); |
| 123 continue; | |
| 124 } | 173 } |
| 125 | |
| 126 // Find the description or friendly name. | |
| 127 ScopedVariant name; | |
| 128 hr = prop_bag->Read(L"Description", name.Receive(), 0); | |
| 129 if (FAILED(hr)) | |
| 130 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0); | |
| 131 | |
| 132 if (SUCCEEDED(hr) && name.type() == VT_BSTR) { | |
| 133 // Ignore all VFW drivers and the special Google Camera Adapter. | |
| 134 // Google Camera Adapter is not a real DirectShow camera device. | |
| 135 // VFW are very old Video for Windows drivers that can not be used. | |
| 136 const wchar_t* str_ptr = V_BSTR(&name); | |
| 137 const int name_length = arraysize(kGoogleCameraAdapter) - 1; | |
| 138 | |
| 139 if ((wcsstr(str_ptr, L"(VFW)") == NULL) && | |
| 140 lstrlenW(str_ptr) < name_length || | |
| 141 (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length, | |
| 142 kGoogleCameraAdapter)))) { | |
| 143 std::string id; | |
| 144 std::string device_name(base::SysWideToUTF8(str_ptr)); | |
| 145 name.Reset(); | |
| 146 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0); | |
| 147 if (FAILED(hr) || name.type() != VT_BSTR) { | |
| 148 id = device_name; | |
| 149 } else { | |
| 150 DCHECK_EQ(name.type(), VT_BSTR); | |
| 151 id = base::SysWideToUTF8(V_BSTR(&name)); | |
| 152 } | |
| 153 | |
| 154 device_names->push_back(VideoCaptureDevice::Name(device_name, id, | |
| 155 VideoCaptureDevice::Name::DIRECT_SHOW)); | |
| 156 } | |
| 157 } | |
| 158 moniker.Release(); | |
| 159 } | 174 } |
| 160 } | 175 } |
| 161 | 176 |
| 162 static void GetDeviceNamesMediaFoundation( | 177 static void GetDeviceNamesMediaFoundation(Names* device_names) { |
| 163 VideoCaptureDevice::Names* device_names) { | |
| 164 DVLOG(1) << " GetDeviceNamesMediaFoundation"; | 178 DVLOG(1) << " GetDeviceNamesMediaFoundation"; |
| 165 ScopedCoMem<IMFActivate*> devices; | 179 ScopedCoMem<IMFActivate*> devices; |
| 166 UINT32 count; | 180 UINT32 count; |
| 167 if (!EnumerateVideoDevicesMediaFoundation(&devices, &count)) | 181 if (!EnumerateVideoDevicesMediaFoundation(&devices, &count)) |
| 168 return; | 182 return; |
| 169 | 183 |
| 170 for (UINT32 i = 0; i < count; ++i) { | 184 for (UINT32 i = 0; i < count; ++i) { |
| 171 ScopedCoMem<wchar_t> name; | 185 ScopedCoMem<wchar_t> name; |
| 172 UINT32 name_size; | 186 UINT32 name_size; |
| 173 HRESULT hr = devices[i]->GetAllocatedString( | 187 HRESULT hr = devices[i]->GetAllocatedString( |
| 174 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size); | 188 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size); |
| 175 if (SUCCEEDED(hr)) { | 189 if (SUCCEEDED(hr)) { |
| 176 ScopedCoMem<wchar_t> id; | 190 ScopedCoMem<wchar_t> id; |
| 177 UINT32 id_size; | 191 UINT32 id_size; |
| 178 hr = devices[i]->GetAllocatedString( | 192 hr = devices[i]->GetAllocatedString( |
| 179 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, | 193 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, |
| 180 &id_size); | 194 &id_size); |
| 181 if (SUCCEEDED(hr)) { | 195 if (SUCCEEDED(hr)) { |
| 182 device_names->push_back(VideoCaptureDevice::Name( | 196 device_names->push_back(Name( |
| 183 base::SysWideToUTF8(std::wstring(name, name_size)), | 197 base::SysWideToUTF8(std::wstring(name, name_size)), |
| 184 base::SysWideToUTF8(std::wstring(id, id_size)), | 198 base::SysWideToUTF8(std::wstring(id, id_size)), |
| 185 VideoCaptureDevice::Name::MEDIA_FOUNDATION)); | 199 Name::MEDIA_FOUNDATION)); |
| 186 } | 200 } |
| 187 } | 201 } |
| 188 if (FAILED(hr)) | 202 if (FAILED(hr)) |
| 189 DLOG(WARNING) << "GetAllocatedString failed: " << std::hex << hr; | 203 DLOG(WARNING) << "GetAllocatedString failed: " << std::hex << hr; |
| 190 devices[i]->Release(); | 204 devices[i]->Release(); |
| 191 } | 205 } |
| 192 } | 206 } |
| 193 | 207 |
| 194 static void GetDeviceSupportedFormatsDirectShow( | 208 static void GetDeviceSupportedFormatsDirectShow(const Name& device, |
| 195 const VideoCaptureDevice::Name& device, | 209 VideoCaptureFormats* formats) { |
| 196 VideoCaptureFormats* formats) { | |
| 197 DVLOG(1) << "GetDeviceSupportedFormatsDirectShow for " << device.name(); | 210 DVLOG(1) << "GetDeviceSupportedFormatsDirectShow for " << device.name(); |
| 198 ScopedComPtr<ICreateDevEnum> dev_enum; | 211 ScopedComPtr<ICreateDevEnum> dev_enum; |
| 199 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, | 212 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, |
| 200 CLSCTX_INPROC); | 213 CLSCTX_INPROC); |
| 201 if (FAILED(hr)) | 214 if (FAILED(hr)) |
| 202 return; | 215 return; |
| 203 | 216 |
| 204 ScopedComPtr<IEnumMoniker> enum_moniker; | 217 ScopedComPtr<IEnumMoniker> enum_moniker; |
| 205 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, | 218 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, |
| 206 enum_moniker.Receive(), 0); | 219 enum_moniker.Receive(), 0); |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 273 0.0f; | 286 0.0f; |
| 274 formats->push_back(format); | 287 formats->push_back(format); |
| 275 DVLOG(1) << device.name() << " resolution: " | 288 DVLOG(1) << device.name() << " resolution: " |
| 276 << format.frame_size.ToString() << ", fps: " << format.frame_rate | 289 << format.frame_size.ToString() << ", fps: " << format.frame_rate |
| 277 << ", pixel format: " << format.pixel_format; | 290 << ", pixel format: " << format.pixel_format; |
| 278 } | 291 } |
| 279 } | 292 } |
| 280 } | 293 } |
| 281 | 294 |
| 282 static void GetDeviceSupportedFormatsMediaFoundation( | 295 static void GetDeviceSupportedFormatsMediaFoundation( |
| 283 const VideoCaptureDevice::Name& device, | 296 const Name& device, |
| 284 VideoCaptureFormats* formats) { | 297 VideoCaptureFormats* formats) { |
| 285 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name(); | 298 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name(); |
| 286 ScopedComPtr<IMFMediaSource> source; | 299 ScopedComPtr<IMFMediaSource> source; |
| 287 if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(), | 300 if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(), |
| 288 source.Receive())) { | 301 source.Receive())) { |
| 289 return; | 302 return; |
| 290 } | 303 } |
| 291 | 304 |
| 292 base::win::ScopedComPtr<IMFSourceReader> reader; | 305 base::win::ScopedComPtr<IMFSourceReader> reader; |
| 293 HRESULT hr = | 306 HRESULT hr = |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 364 // 8 in non-Metro mode. | 377 // 8 in non-Metro mode. |
| 365 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); | 378 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
| 366 use_media_foundation_ = (base::win::IsMetroProcess() && | 379 use_media_foundation_ = (base::win::IsMetroProcess() && |
| 367 !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) || | 380 !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) || |
| 368 (base::win::GetVersion() >= base::win::VERSION_WIN7 && | 381 (base::win::GetVersion() >= base::win::VERSION_WIN7 && |
| 369 cmd_line->HasSwitch(switches::kForceMediaFoundationVideoCapture)); | 382 cmd_line->HasSwitch(switches::kForceMediaFoundationVideoCapture)); |
| 370 } | 383 } |
| 371 | 384 |
| 372 | 385 |
| 373 scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::Create( | 386 scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::Create( |
| 374 const VideoCaptureDevice::Name& device_name) { | 387 const Name& device_name) { |
| 375 DCHECK(thread_checker_.CalledOnValidThread()); | 388 DCHECK(thread_checker_.CalledOnValidThread()); |
| 376 scoped_ptr<VideoCaptureDevice> device; | 389 scoped_ptr<VideoCaptureDevice> device; |
| 377 if (device_name.capture_api_type() == | 390 if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) { |
| 378 VideoCaptureDevice::Name::MEDIA_FOUNDATION) { | |
| 379 DCHECK(PlatformSupportsMediaFoundation()); | 391 DCHECK(PlatformSupportsMediaFoundation()); |
| 380 device.reset(new VideoCaptureDeviceMFWin(device_name)); | 392 device.reset(new VideoCaptureDeviceMFWin(device_name)); |
| 381 DVLOG(1) << " MediaFoundation Device: " << device_name.name(); | 393 DVLOG(1) << " MediaFoundation Device: " << device_name.name(); |
| 382 ScopedComPtr<IMFMediaSource> source; | 394 ScopedComPtr<IMFMediaSource> source; |
| 383 if (!CreateVideoCaptureDeviceMediaFoundation(device_name.id().c_str(), | 395 if (!CreateVideoCaptureDeviceMediaFoundation(device_name.id().c_str(), |
| 384 source.Receive())) { | 396 source.Receive())) { |
| 385 return scoped_ptr<VideoCaptureDevice>(); | 397 return scoped_ptr<VideoCaptureDevice>(); |
| 386 } | 398 } |
| 387 if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source)) | 399 if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source)) |
| 388 device.reset(); | 400 device.reset(); |
| 389 } else { | 401 } else { |
| 390 DCHECK_EQ(device_name.capture_api_type(), | 402 DCHECK(device_name.capture_api_type() == Name::DIRECT_SHOW || |
| 391 VideoCaptureDevice::Name::DIRECT_SHOW); | 403 device_name.capture_api_type() == Name::DIRECT_SHOW_WDM); |
| 392 device.reset(new VideoCaptureDeviceWin(device_name)); | 404 device.reset(new VideoCaptureDeviceWin(device_name)); |
| 393 DVLOG(1) << " DirectShow Device: " << device_name.name(); | 405 DVLOG(1) << " DirectShow Device: " << device_name.name(); |
| 394 if (!static_cast<VideoCaptureDeviceWin*>(device.get())->Init()) | 406 if (!static_cast<VideoCaptureDeviceWin*>(device.get())->Init()) |
| 395 device.reset(); | 407 device.reset(); |
| 396 } | 408 } |
| 397 return device.Pass(); | 409 return device.Pass(); |
| 398 } | 410 } |
| 399 | 411 |
| 400 void VideoCaptureDeviceFactoryWin::GetDeviceNames( | 412 void VideoCaptureDeviceFactoryWin::GetDeviceNames(Names* device_names) { |
| 401 VideoCaptureDevice::Names* device_names) { | |
| 402 DCHECK(thread_checker_.CalledOnValidThread()); | 413 DCHECK(thread_checker_.CalledOnValidThread()); |
| 403 if (use_media_foundation_) | 414 if (use_media_foundation_) |
| 404 GetDeviceNamesMediaFoundation(device_names); | 415 GetDeviceNamesMediaFoundation(device_names); |
| 405 else | 416 else |
| 406 GetDeviceNamesDirectShow(device_names); | 417 GetDeviceNamesDirectShow(device_names); |
| 407 } | 418 } |
| 408 | 419 |
| 409 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats( | 420 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats( |
| 410 const VideoCaptureDevice::Name& device, | 421 const Name& device, |
| 411 VideoCaptureFormats* formats) { | 422 VideoCaptureFormats* formats) { |
| 412 DCHECK(thread_checker_.CalledOnValidThread()); | 423 DCHECK(thread_checker_.CalledOnValidThread()); |
| 413 if (use_media_foundation_) | 424 if (use_media_foundation_) |
| 414 GetDeviceSupportedFormatsMediaFoundation(device, formats); | 425 GetDeviceSupportedFormatsMediaFoundation(device, formats); |
| 415 else | 426 else |
| 416 GetDeviceSupportedFormatsDirectShow(device, formats); | 427 GetDeviceSupportedFormatsDirectShow(device, formats); |
| 417 } | 428 } |
| 418 | 429 |
| 419 } // namespace media | 430 } // namespace media |
| OLD | NEW |