1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "media/video/capture/win/video_capture_device_win.h" 6 7 #include <algorithm> 8 #include <list> 9 10 #include "base/command_line.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/sys_string_conversions.h" 13 #include "base/win/scoped_variant.h" 14 #include "media/base/media_switches.h" 15 #include "media/video/capture/win/video_capture_device_mf_win.h" 16 17 using base::win::ScopedComPtr; 18 using base::win::ScopedVariant; 19 20 namespace media { 21 namespace { 22 23 // Finds and creates a DirectShow Video Capture filter matching the device_name. 24 HRESULT GetDeviceFilter(const VideoCaptureDevice::Name& device_name, 25 IBaseFilter** filter) { 26 DCHECK(filter); 27 28 ScopedComPtr<ICreateDevEnum> dev_enum; 29 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, 30 CLSCTX_INPROC); 31 if (FAILED(hr)) 32 return hr; 33 34 ScopedComPtr<IEnumMoniker> enum_moniker; 35 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, 36 enum_moniker.Receive(), 0); 37 // CreateClassEnumerator returns S_FALSE on some Windows OS 38 // when no camera exist. Therefore the FAILED macro can't be used. 39 if (hr != S_OK) 40 return NULL; 41 42 ScopedComPtr<IMoniker> moniker; 43 ScopedComPtr<IBaseFilter> capture_filter; 44 DWORD fetched = 0; 45 while (enum_moniker->Next(1, moniker.Receive(), &fetched) == S_OK) { 46 ScopedComPtr<IPropertyBag> prop_bag; 47 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); 48 if (FAILED(hr)) { 49 moniker.Release(); 50 continue; 51 } 52 53 // Find the description or friendly name. 54 static const wchar_t* kPropertyNames[] = { 55 L"DevicePath", L"Description", L"FriendlyName" 56 }; 57 ScopedVariant name; 58 for (size_t i = 0; 59 i < arraysize(kPropertyNames) && name.type() != VT_BSTR; ++i) { 60 prop_bag->Read(kPropertyNames[i], name.Receive(), 0); 61 } 62 if (name.type() == VT_BSTR) { 63 std::string device_path(base::SysWideToUTF8(V_BSTR(&name))); 64 if (device_path.compare(device_name.id()) == 0) { 65 // We have found the requested device 66 hr = moniker->BindToObject(0, 0, IID_IBaseFilter, 67 capture_filter.ReceiveVoid()); 68 DVPLOG_IF(2, FAILED(hr)) << "Failed to bind camera filter."; 69 break; 70 } 71 } 72 moniker.Release(); 73 } 74 75 *filter = capture_filter.Detach(); 76 if (!*filter && SUCCEEDED(hr)) 77 hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); 78 79 return hr; 80 } 81 82 // Check if a Pin matches a category. 83 bool PinMatchesCategory(IPin* pin, REFGUID category) { 84 DCHECK(pin); 85 bool found = false; 86 ScopedComPtr<IKsPropertySet> ks_property; 87 HRESULT hr = ks_property.QueryFrom(pin); 88 if (SUCCEEDED(hr)) { 89 GUID pin_category; 90 DWORD return_value; 91 hr = ks_property->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0, 92 &pin_category, sizeof(pin_category), &return_value); 93 if (SUCCEEDED(hr) && (return_value == sizeof(pin_category))) { 94 found = (pin_category == category); 95 } 96 } 97 return found; 98 } 99 100 // Finds a IPin on a IBaseFilter given the direction an category. 101 HRESULT GetPin(IBaseFilter* filter, PIN_DIRECTION pin_dir, REFGUID category, 102 IPin** pin) { 103 DCHECK(pin); 104 ScopedComPtr<IEnumPins> pin_emum; 105 HRESULT hr = filter->EnumPins(pin_emum.Receive()); 106 if (pin_emum == NULL) 107 return hr; 108 109 // Get first unconnected pin. 110 hr = pin_emum->Reset(); // set to first pin 111 while ((hr = pin_emum->Next(1, pin, NULL)) == S_OK) { 112 PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1); 113 hr = (*pin)->QueryDirection(&this_pin_dir); 114 if (pin_dir == this_pin_dir) { 115 if (category == GUID_NULL || PinMatchesCategory(*pin, category)) 116 return S_OK; 117 } 118 (*pin)->Release(); 119 } 120 121 return E_FAIL; 122 } 123 124 // Release the format block for a media type. 125 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx 126 void FreeMediaType(AM_MEDIA_TYPE* mt) { 127 if (mt->cbFormat != 0) { 128 CoTaskMemFree(mt->pbFormat); 129 mt->cbFormat = 0; 130 mt->pbFormat = NULL; 131 } 132 if (mt->pUnk != NULL) { 133 NOTREACHED(); 134 // pUnk should not be used. 135 mt->pUnk->Release(); 136 mt->pUnk = NULL; 137 } 138 } 139 140 // Delete a media type structure that was allocated on the heap. 141 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx 142 void DeleteMediaType(AM_MEDIA_TYPE* mt) { 143 if (mt != NULL) { 144 FreeMediaType(mt); 145 CoTaskMemFree(mt); 146 } 147 } 148 149 } // namespace 150 151 // static 152 void VideoCaptureDevice::GetDeviceNames(Names* device_names) { 153 Names::iterator it; 154 155 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); 156 if (VideoCaptureDeviceMFWin::PlatformSupported() && 157 !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) { 158 VideoCaptureDeviceMFWin::GetDeviceNames(device_names); 159 } 160 // Retrieve the devices with DirectShow (DS) interface. They might (partially) 161 // overlap with the MediaFoundation (MF), so the list has to be consolidated. 162 Names temp_names; 163 VideoCaptureDeviceWin::GetDeviceNames(&temp_names); 164 165 // Merge the DS devices into the MF device list, and next remove 166 // the duplicates, giving priority to the MF "versions". 167 device_names->merge(temp_names); 168 device_names->unique(); 169 } 170 171 // static 172 VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) { 173 VideoCaptureDevice* ret = NULL; 174 if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) { 175 DCHECK(VideoCaptureDeviceMFWin::PlatformSupported()); 176 scoped_ptr<VideoCaptureDeviceMFWin> device( 177 new VideoCaptureDeviceMFWin(device_name)); 178 DVLOG(1) << " MediaFoundation Device: " << device_name.name(); 179 if (device->Init()) 180 ret = device.release(); 181 } else if (device_name.capture_api_type() == Name::DIRECT_SHOW) { 182 scoped_ptr<VideoCaptureDeviceWin> device( 183 new VideoCaptureDeviceWin(device_name)); 184 DVLOG(1) << " DirectShow Device: " << device_name.name(); 185 if (device->Init()) 186 ret = device.release(); 187 } else{ 188 NOTREACHED() << " Couldn't recognize VideoCaptureDevice type"; 189 } 190 191 return ret; 192 } 193 194 // static 195 void VideoCaptureDeviceWin::GetDeviceNames(Names* device_names) { 196 DCHECK(device_names); 197 198 ScopedComPtr<ICreateDevEnum> dev_enum; 199 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, 200 CLSCTX_INPROC); 201 if (FAILED(hr)) 202 return; 203 204 ScopedComPtr<IEnumMoniker> enum_moniker; 205 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, 206 enum_moniker.Receive(), 0); 207 // CreateClassEnumerator returns S_FALSE on some Windows OS 208 // when no camera exist. Therefore the FAILED macro can't be used. 209 if (hr != S_OK) 210 return; 211 212 device_names->clear(); 213 214 // Name of a fake DirectShow filter that exist on computers with 215 // GTalk installed. 216 static const char kGoogleCameraAdapter[] = "google camera adapter"; 217 218 // Enumerate all video capture devices. 219 ScopedComPtr<IMoniker> moniker; 220 int index = 0; 221 while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) { 222 ScopedComPtr<IPropertyBag> prop_bag; 223 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); 224 if (FAILED(hr)) { 225 moniker.Release(); 226 continue; 227 } 228 229 // Find the description or friendly name. 230 ScopedVariant name; 231 hr = prop_bag->Read(L"Description", name.Receive(), 0); 232 if (FAILED(hr)) 233 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0); 234 235 if (SUCCEEDED(hr) && name.type() == VT_BSTR) { 236 // Ignore all VFW drivers and the special Google Camera Adapter. 237 // Google Camera Adapter is not a real DirectShow camera device. 238 // VFW is very old Video for Windows drivers that can not be used. 239 const wchar_t* str_ptr = V_BSTR(&name); 240 const int name_length = arraysize(kGoogleCameraAdapter) - 1; 241 242 if ((wcsstr(str_ptr, L"(VFW)") == NULL) && 243 lstrlenW(str_ptr) < name_length || 244 (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length, 245 kGoogleCameraAdapter)))) { 246 std::string id; 247 std::string device_name(base::SysWideToUTF8(str_ptr)); 248 name.Reset(); 249 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0); 250 if (FAILED(hr) || name.type() != VT_BSTR) { 251 id = device_name; 252 } else { 253 DCHECK_EQ(name.type(), VT_BSTR); 254 id = base::SysWideToUTF8(V_BSTR(&name)); 255 } 256 257 device_names->push_back(Name(device_name, id, Name::DIRECT_SHOW)); 258 } 259 } 260 moniker.Release(); 261 } 262 } 263 264 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name) 265 : device_name_(device_name), 266 state_(kIdle), 267 observer_(NULL) { 268 DetachFromThread(); 269 } 270 271 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() { 272 DCHECK(CalledOnValidThread()); 273 if (media_control_) 274 media_control_->Stop(); 275 276 if (graph_builder_) { 277 if (sink_filter_) { 278 graph_builder_->RemoveFilter(sink_filter_); 279 sink_filter_ = NULL; 280 } 281 282 if (capture_filter_) 283 graph_builder_->RemoveFilter(capture_filter_); 284 285 if (mjpg_filter_) 286 graph_builder_->RemoveFilter(mjpg_filter_); 287 } 288 } 289 290 bool VideoCaptureDeviceWin::Init() { 291 DCHECK(CalledOnValidThread()); 292 HRESULT hr = GetDeviceFilter(device_name_, capture_filter_.Receive()); 293 if (!capture_filter_) { 294 DVLOG(2) << "Failed to create capture filter."; 295 return false; 296 } 297 298 hr = GetPin(capture_filter_, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE, 299 output_capture_pin_.Receive()); 300 if (!output_capture_pin_) { 301 DVLOG(2) << "Failed to get capture output pin"; 302 return false; 303 } 304 305 // Create the sink filter used for receiving Captured frames. 306 sink_filter_ = new SinkFilter(this); 307 if (sink_filter_ == NULL) { 308 DVLOG(2) << "Failed to create send filter"; 309 return false; 310 } 311 312 input_sink_pin_ = sink_filter_->GetPin(0); 313 314 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL, 315 CLSCTX_INPROC_SERVER); 316 if (FAILED(hr)) { 317 DVLOG(2) << "Failed to create graph builder."; 318 return false; 319 } 320 321 hr = graph_builder_.QueryInterface(media_control_.Receive()); 322 if (FAILED(hr)) { 323 DVLOG(2) << "Failed to create media control builder."; 324 return false; 325 } 326 327 hr = graph_builder_->AddFilter(capture_filter_, NULL); 328 if (FAILED(hr)) { 329 DVLOG(2) << "Failed to add the capture device to the graph."; 330 return false; 331 } 332 333 hr = graph_builder_->AddFilter(sink_filter_, NULL); 334 if (FAILED(hr)) { 335 DVLOG(2)<< "Failed to add the send filter to the graph."; 336 return false; 337 } 338 339 return CreateCapabilityMap(); 340 } 341 342 void VideoCaptureDeviceWin::Allocate( 343 const VideoCaptureCapability& capture_format, 344 VideoCaptureDevice::EventHandler* observer) { 345 DCHECK(CalledOnValidThread()); 346 if (state_ != kIdle) 347 return; 348 349 observer_ = observer; 350 351 // Get the camera capability that best match the requested resolution. 352 const VideoCaptureCapabilityWin& found_capability = 353 capabilities_.GetBestMatchedCapability(capture_format.width, 354 capture_format.height, 355 capture_format.frame_rate); 356 VideoCaptureCapability capability = found_capability; 357 358 // Reduce the frame rate if the requested frame rate is lower 359 // than the capability. 360 if (capability.frame_rate > capture_format.frame_rate) 361 capability.frame_rate = capture_format.frame_rate; 362 363 AM_MEDIA_TYPE* pmt = NULL; 364 VIDEO_STREAM_CONFIG_CAPS caps; 365 366 ScopedComPtr<IAMStreamConfig> stream_config; 367 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive()); 368 if (FAILED(hr)) { 369 SetErrorState("Can't get the Capture format settings"); 370 return; 371 } 372 373 // Get the windows capability from the capture device. 374 hr = stream_config->GetStreamCaps(found_capability.stream_index, &pmt, 375 reinterpret_cast<BYTE*>(&caps)); 376 if (SUCCEEDED(hr)) { 377 if (pmt->formattype == FORMAT_VideoInfo) { 378 VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat); 379 if (capability.frame_rate > 0) 380 h->AvgTimePerFrame = kSecondsToReferenceTime / capability.frame_rate; 381 } 382 // Set the sink filter to request this capability. 383 sink_filter_->SetRequestedMediaCapability(capability); 384 // Order the capture device to use this capability. 385 hr = stream_config->SetFormat(pmt); 386 } 387 388 if (FAILED(hr)) 389 SetErrorState("Failed to set capture device output format"); 390 391 if (capability.color == VideoCaptureCapability::kMJPEG && 392 !mjpg_filter_.get()) { 393 // Create MJPG filter if we need it. 394 hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC); 395 396 if (SUCCEEDED(hr)) { 397 GetPin(mjpg_filter_, PINDIR_INPUT, GUID_NULL, input_mjpg_pin_.Receive()); 398 GetPin(mjpg_filter_, PINDIR_OUTPUT, GUID_NULL, 399 output_mjpg_pin_.Receive()); 400 hr = graph_builder_->AddFilter(mjpg_filter_, NULL); 401 } 402 403 if (FAILED(hr)) { 404 mjpg_filter_.Release(); 405 input_mjpg_pin_.Release(); 406 output_mjpg_pin_.Release(); 407 } 408 } 409 410 if (capability.color == VideoCaptureCapability::kMJPEG && 411 mjpg_filter_.get()) { 412 // Connect the camera to the MJPEG decoder. 413 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_mjpg_pin_, 414 NULL); 415 // Connect the MJPEG filter to the Capture filter. 416 hr += graph_builder_->ConnectDirect(output_mjpg_pin_, input_sink_pin_, 417 NULL); 418 } else { 419 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_sink_pin_, 420 NULL); 421 } 422 423 if (FAILED(hr)) { 424 SetErrorState("Failed to connect the Capture graph."); 425 return; 426 } 427 428 hr = media_control_->Pause(); 429 if (FAILED(hr)) { 430 SetErrorState("Failed to Pause the Capture device. " 431 "Is it already occupied?"); 432 return; 433 } 434 435 // Get the capability back from the sink filter after the filter have been 436 // connected. 437 const VideoCaptureCapability& used_capability 438 = sink_filter_->ResultingCapability(); 439 observer_->OnFrameInfo(used_capability); 440 441 state_ = kAllocated; 442 } 443 444 void VideoCaptureDeviceWin::Start() { 445 DCHECK(CalledOnValidThread()); 446 if (state_ != kAllocated) 447 return; 448 449 HRESULT hr = media_control_->Run(); 450 if (FAILED(hr)) { 451 SetErrorState("Failed to start the Capture device."); 452 return; 453 } 454 455 state_ = kCapturing; 456 } 457 458 void VideoCaptureDeviceWin::Stop() { 459 DCHECK(CalledOnValidThread()); 460 if (state_ != kCapturing) 461 return; 462 463 HRESULT hr = media_control_->Stop(); 464 if (FAILED(hr)) { 465 SetErrorState("Failed to stop the capture graph."); 466 return; 467 } 468 469 state_ = kAllocated; 470 } 471 472 void VideoCaptureDeviceWin::DeAllocate() { 473 DCHECK(CalledOnValidThread()); 474 if (state_ == kIdle) 475 return; 476 477 HRESULT hr = media_control_->Stop(); 478 graph_builder_->Disconnect(output_capture_pin_); 479 graph_builder_->Disconnect(input_sink_pin_); 480 481 // If the _mjpg filter exist disconnect it even if it has not been used. 482 if (mjpg_filter_) { 483 graph_builder_->Disconnect(input_mjpg_pin_); 484 graph_builder_->Disconnect(output_mjpg_pin_); 485 } 486 487 if (FAILED(hr)) { 488 SetErrorState("Failed to Stop the Capture device"); 489 return; 490 } 491 492 state_ = kIdle; 493 } 494 495 const VideoCaptureDevice::Name& VideoCaptureDeviceWin::device_name() { 496 DCHECK(CalledOnValidThread()); 497 return device_name_; 498 } 499 500 // Implements SinkFilterObserver::SinkFilterObserver. 501 void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer, 502 int length) { 503 observer_->OnIncomingCapturedFrame(buffer, length, base::Time::Now(), 504 0, false, false); 505 } 506 507 bool VideoCaptureDeviceWin::CreateCapabilityMap() { 508 DCHECK(CalledOnValidThread()); 509 ScopedComPtr<IAMStreamConfig> stream_config; 510 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive()); 511 if (FAILED(hr)) { 512 DVLOG(2) << "Failed to get IAMStreamConfig interface from " 513 "capture device"; 514 return false; 515 } 516 517 // Get interface used for getting the frame rate. 518 ScopedComPtr<IAMVideoControl> video_control; 519 hr = capture_filter_.QueryInterface(video_control.Receive()); 520 DVLOG_IF(2, FAILED(hr)) << "IAMVideoControl Interface NOT SUPPORTED"; 521 522 AM_MEDIA_TYPE* media_type = NULL; 523 VIDEO_STREAM_CONFIG_CAPS caps; 524 int count, size; 525 526 hr = stream_config->GetNumberOfCapabilities(&count, &size); 527 if (FAILED(hr)) { 528 DVLOG(2) << "Failed to GetNumberOfCapabilities"; 529 return false; 530 } 531 532 for (int i = 0; i < count; ++i) { 533 hr = stream_config->GetStreamCaps(i, &media_type, 534 reinterpret_cast<BYTE*>(&caps)); 535 if (FAILED(hr)) { 536 DVLOG(2) << "Failed to GetStreamCaps"; 537 return false; 538 } 539 540 if (media_type->majortype == MEDIATYPE_Video && 541 media_type->formattype == FORMAT_VideoInfo) { 542 VideoCaptureCapabilityWin capability(i); 543 REFERENCE_TIME time_per_frame = 0; 544 545 VIDEOINFOHEADER* h = 546 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); 547 capability.width = h->bmiHeader.biWidth; 548 capability.height = h->bmiHeader.biHeight; 549 time_per_frame = h->AvgTimePerFrame; 550 551 // Try to get the max frame rate from IAMVideoControl. 552 if (video_control) { 553 LONGLONG* max_fps_ptr; 554 LONG list_size; 555 SIZE size; 556 size.cx = capability.width; 557 size.cy = capability.height; 558 559 // GetFrameRateList doesn't return max frame rate always 560 // eg: Logitech Notebook. This may be due to a bug in that API 561 // because GetFrameRateList array is reversed in the above camera. So 562 // a util method written. Can't assume the first value will return 563 // the max fps. 564 hr = video_control->GetFrameRateList(output_capture_pin_, i, size, 565 &list_size, &max_fps_ptr); 566 567 if (SUCCEEDED(hr) && list_size > 0) { 568 int min_time = *std::min_element(max_fps_ptr, 569 max_fps_ptr + list_size); 570 capability.frame_rate = (min_time > 0) ? 571 kSecondsToReferenceTime / min_time : 0; 572 } else { 573 // Get frame rate from VIDEOINFOHEADER. 574 capability.frame_rate = (time_per_frame > 0) ? 575 static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0; 576 } 577 } else { 578 // Get frame rate from VIDEOINFOHEADER since IAMVideoControl is 579 // not supported. 580 capability.frame_rate = (time_per_frame > 0) ? 581 static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0; 582 } 583 // DirectShow works at the moment only on integer frame_rate but the 584 // best capability matching class works on rational frame rates. 585 capability.frame_rate_numerator = capability.frame_rate; 586 capability.frame_rate_denominator = 1; 587 588 // We can't switch MEDIATYPE :~(. 589 if (media_type->subtype == kMediaSubTypeI420) { 590 capability.color = VideoCaptureCapability::kI420; 591 } else if (media_type->subtype == MEDIASUBTYPE_IYUV) { 592 // This is identical to kI420. 593 capability.color = VideoCaptureCapability::kI420; 594 } else if (media_type->subtype == MEDIASUBTYPE_RGB24) { 595 capability.color = VideoCaptureCapability::kRGB24; 596 } else if (media_type->subtype == MEDIASUBTYPE_YUY2) { 597 capability.color = VideoCaptureCapability::kYUY2; 598 } else if (media_type->subtype == MEDIASUBTYPE_MJPG) { 599 capability.color = VideoCaptureCapability::kMJPEG; 600 } else if (media_type->subtype == MEDIASUBTYPE_UYVY) { 601 capability.color = VideoCaptureCapability::kUYVY; 602 } else if (media_type->subtype == MEDIASUBTYPE_ARGB32) { 603 capability.color = VideoCaptureCapability::kARGB; 604 } else { 605 WCHAR guid_str[128]; 606 StringFromGUID2(media_type->subtype, guid_str, arraysize(guid_str)); 607 DVLOG(2) << "Device supports (also) an unknown media type " << guid_str; 608 continue; 609 } 610 capabilities_.Add(capability); 611 } 612 DeleteMediaType(media_type); 613 media_type = NULL; 614 } 615 616 return !capabilities_.empty(); 617 } 618 619 void VideoCaptureDeviceWin::SetErrorState(const char* reason) { 620 DCHECK(CalledOnValidThread()); 621 DVLOG(1) << reason; 622 state_ = kError; 623 observer_->OnError(); 624 } 625 } // namespace media 626