preface

Why analyze enumationDevice source code? Device_id and gourp_id can be obtained by calling enumationDevice with JS. However, both device_id and group_id are hash processed, so the original data cannot be obtained and what the original data is is not known. Now you need to be able to figure out the whole generation process of device_id and group_id through the source code, and then use the data returned by TinyCV to generate the same hash value as it, so as to achieve the corresponding

Train of thought

  1. Hard copy chromium source code to find out the generation rules
  2. Compile Chormium, source mode

Recommend an online browsing website of chromium source code, the local computer too card source.chromium.org/chromium/ch…

tinycv-node

If you are not interested in the principle, I have encapsulated a node library, from which you can directly generate the corresponding Device_id for Chromium, link to TinyCV, and enumerate all devices through DeviceManager. The Device path can be converted into the Device ID corresponding to Chromium through the Device class

blink – MediaDevices::enumerateDevices

File: chromium/third_party/blink/renderer/modules/mediastream/media_devices. Cc: 96

ScriptPromise MediaDevices::enumerateDevices(ScriptState* script_state, ExceptionState& exception_state) {
  UpdateWebRTCMethodCount(RTCAPIName::kEnumerateDevices);
  if(! script_state->ContextIsValid()) {
    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                      "Current frame is detached.");
    return ScriptPromise(a); }auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
  ScriptPromise promise = resolver->Promise(a); requests_.insert(resolver);

  LocalFrame* frame = LocalDOMWindow::From(script_state)->GetFrame(a);GetDispatcherHost(frame)->EnumerateDevices(
      true /* audio input */.true /* video input */.true /* audio output */.true /* request_video_input_capabilities */.true /* request_audio_input_capabilities */,
      WTF::Bind(&MediaDevices::DevicesEnumerated, WrapPersistent(this),
                WrapPersistent(resolver)));
  return promise;
}
Copy the code

The above function is the specific implementation when JS calls enumerateDevices. First of all, we need to know the multi-process framework of Chromium. All the codes in Blink are run in the render process, which is only responsible for DOM structure parsing and JS parsing execution. GetDispatcherHost(frame)->EnumerateDevices() ->EnumerateDevices() It is in the Browser process, while Chromium has its own multi-process communication framework MoJO. Here GetDispatcherHost is to hand over the actual operation of EnumerateDevices to browser process through Mojo. You can look at the GetDispatcerHost implementation below


blink – MediaDevices::GetDispatcherHost

chromium/third_party/blink/renderer/modules/mediastream/media_devices.cc:502

const mojo::Remote<mojom::blink::MediaDevicesDispatcherHost>&
MediaDevices::GetDispatcherHost(LocalFrame* frame) {
  if(! dispatcher_host_) {/ /...
  }

  return dispatcher_host_;
}
Copy the code

Mojom is a set of interface description language (IDL) developed in chromium for mojo interprocess communication, which is similar to protobuf. The corresponding c++ files can be generated through the interface description language. More information about mojo is provided. There are details on the Chromium website, and you can see the object from the return value

mojom::blink::MediaDevicesDispatcherHost

chromium/third_party/blink/public/mojom/mediastream/media_devices.mojom

// This object lives in the browser and is responsible for processing device
// enumeration requests and managing subscriptions for device-change
// notifications.
interface MediaDevicesDispatcherHost {
  EnumerateDevices(bool request_audio_input,
                   bool request_video_input,
                   bool request_audio_output,
                   bool request_video_input_capabilities,
                   bool request_audio_input_capabilities)
      => (array<array<MediaDeviceInfo>> enumeration,
          array<VideoInputDeviceCapabilities> video_input_device_capabilities,
          array<AudioInputDeviceCapabilities> audio_input_device_capabilities);
}
Copy the code

The code above is mojom MediaDevicesDispatcherHost interfaces defined object, it generates a corresponding at compile time. H file, c + + side only needs to inherit the MediaDevicesDispatherHost, And implement the corresponding interface, then the idea is to search globally who inherits the class

browser – content::MediaDeviceDispatcherHost

chromium/content/browser/renderer_host/media/media_devices_dispatcher_host.h

class CONTENT_EXPORT MediaDevicesDispatcherHost
    : public blink::mojom::MediaDevicesDispatcherHost {
 public:
  / /...

  // blink::mojom::MediaDevicesDispatcherHost implementation.
  void EnumerateDevices(bool request_audio_input,
                        bool request_video_input,
                        bool request_audio_output,
                        bool request_video_input_capabilities,
                        bool request_audio_input_capabilities,
                        EnumerateDevicesCallback client_callback) override;
  / /...
Copy the code

Can see inside the content to achieve the blink: : mojom: : MediaDevicesDispatcherHost, from code comments can also clearly see he is mojom interface implementation, here we are on the right track.

MediaDeviceDispatcherHost::EnumerateDevices

chromium/content/browser/renderer_host/media/media_devices_dispatcher_host.cc:123

void MediaDevicesDispatcherHost::EnumerateDevices(
    bool request_audio_input,
    bool request_video_input,
    bool request_audio_output,
    bool request_video_input_capabilities,
    bool request_audio_input_capabilities,
    EnumerateDevicesCallback client_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  / /...
  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
  devices_to_enumerate[static_cast<size_t>(
      MediaDeviceType::MEDIA_AUDIO_INPUT)] = request_audio_input;
  devices_to_enumerate[static_cast<size_t>(
      MediaDeviceType::MEDIA_VIDEO_INPUT)] = request_video_input;
  devices_to_enumerate[static_cast<size_t>(
      MediaDeviceType::MEDIA_AUDIO_OUTPUT)] = request_audio_output;

  media_stream_manager_->media_devices_manager() - >EnumerateDevices(
      render_process_id_, render_frame_id_, devices_to_enumerate,
      request_video_input_capabilities, request_audio_input_capabilities,
      std::move(client_callback));
}
Copy the code

The above function will be called to media_devices_manager function to MediaDeviceManager: : EnumerateDevices

MediaDeviceManger::EnumerateDevices

chromium/content/browser/renderer_host/media/media_devices_manager.cc

void MediaDevicesManager::EnumerateDevices(
    int render_process_id,
    int render_frame_id,
    const BoolDeviceTypes& requested_types,
    bool request_video_input_capabilities,
    bool request_audio_input_capabilities,
    EnumerateDevicesCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  base::PostTaskAndReplyWithResult(
      GetUIThreadTaskRunner({}).get(), FROM_HERE,
      base::BindOnce(salt_and_origin_callback_, render_process_id,
                     render_frame_id),
      base::BindOnce(&MediaDevicesManager::CheckPermissionsForEnumerateDevices,
                     weak_factory_.GetWeakPtr(), render_process_id,
                     render_frame_id, requested_types,
                     request_video_input_capabilities,
                     request_audio_input_capabilities, std::move(callback)));
}

Copy the code

Code above USES a characteristic of a chromium multi-threaded communications, PostTaskAndReplayWithResult function salt_and_origin_callback_ this function will be Post to the UI thread, Then go after the result of back off to CheckPermissionsForEnumerateDevices, this CheckPermissionsForEnumerateDevices in IO thread execution, About salt_and_origin_callback_ this member variable, it is actually a function address, the corresponding function is GetMediaDeviceSaltAndOrigin, through the name of the function in fact it is not hard to guess, “Device_id” and “group_id” must be added to hash

GetMediaDeviceSaltAndOrigin

chromium/content/content/browser/media/media_devices_util.cc

MediaDeviceSaltAndOrigin GetMediaDeviceSaltAndOrigin(int render_process_id,
                                                     int render_frame_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  RenderFrameHostImpl* frame_host =
      RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
  RenderProcessHost* process_host =
      RenderProcessHost::FromID(render_process_id);

  / /...

  if (frame_host) {
    / /...
    origin = frame_host->GetLastCommittedOrigin(a); frame_salt = frame_host->GetMediaDeviceIDSaltBase(a); }bool are_persistent_ids_allowed = false;
  std::string device_id_salt;
  std::string group_id_salt;
  if (process_host) {
    are_persistent_ids_allowed =
        GetContentClient() - >browser() - >ArePersistentMediaDeviceIDsAllowed(
            process_host->GetBrowserContext(), url, site_for_cookies,
            top_level_origin);
    device_id_salt = process_host->GetBrowserContext() - >GetMediaDeviceIDSalt(a); group_id_salt = device_id_salt; }// If persistent IDs are not allowed, append |frame_salt| to make it
  // specific to the current document.
  if(! are_persistent_ids_allowed) device_id_salt += frame_salt;// |group_id_salt| must be unique per document, but it must also change if
  // cookies are cleared. Also, it must be different from |device_id_salt|,
  // thus appending a constant.
  group_id_salt += frame_salt + "groupid";

  return {std::move(device_id_salt), std::move(group_id_salt),
          std::move(origin)};
}
Copy the code

The first thing I know so far is that device_id does not change even if you close the browser and restart the device. It only changes when you plug and unplug the device. This confirms that the above frame_salt is not used. (ignore group_id for now, group_id changes because frame_salt changes), note: Process_host ->GetBrowserContext()->GetMediaDeviceIDSalt()

ProfileImpl::GetMediaDeviceIDSalt

chromium/chrome/browser/profiles/profile_impl.cc:1341

std::string ProfileImpl::GetMediaDeviceIDSalt(a) {
  return media_device_id_salt_->GetSalt(a); }Copy the code

MediaDeviceIDSalt::GetSalt

chromium/chrome/browser/media/media_device_id_salt.cc

std::string MediaDeviceIDSalt::GetSalt(a) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return media_device_id_salt_.GetValue(a); }Copy the code

The GetSalt function returns the value in media_device_id_salt. Now we just need to find the initialization and SetValue of the media_device_id_salt object

MediaDeviceIDSalt::MediaDeviceIDSalt

chromium/chrome/browser/media/media_device_id_salt.cc

MediaDeviceIDSalt::MediaDeviceIDSalt(PrefService* pref_service) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  media_device_id_salt_.Init(prefs::kMediaDeviceIdSalt, pref_service);
  if (media_device_id_salt_.GetValue().empty()) {
    media_device_id_salt_.SetValue(
        content::BrowserContext::CreateRandomMediaDeviceIDSalt()); }}Copy the code

Found in MediaDeviceIDSalt constructor initialization, but there is a big question, the SetValue set here is a random value, CreateRandomMediaDeviceIDSalt function jump in implementation actually is to generate a random value, But device_id doesn’t change even after you restart the browser, so the salt can’t be a random value, either way

  1. The whole analysis is wrong. GetSalt didn’t go here. Could it have gone somewhere else? (After all, it is hard to see the source code, there is no mode, so it is possible)
  2. The media_device_id_salt.getValue ().empty() function is not satisfied and thus does not go to SetValue for a random value

Device_id & group_id salt Generate a conclusion

Source modality after the conclusion: After application startup, call enumerateDevices later found out that he will call directly to the MediaDeviceIDSalt: : GetSalt (), the inside to GetValue value already exists, then a breakpoint downloading MediaDeviceIDSalt inside a constructor, Media_device_id_salt_.getvalue ().empty() : media_device_id_salt_.getValue ().empty() : media_device_id_salt_.getValue (); Then I looked at it for a long time and only found that media_device_id_salt_.setValue would copy the salt, I was confused. Then I went to chromium to profile and found that it would save all the configuration to a file. If media_device_id_salt.getValue ().empty() is not empty, it is possible

  1. EnumerateDevices are called directly from the browser. As a result, the entire profile is already initialized and written to a file, which makes no sense in the first mode
  2. Subsequent retuning needs to realize that this value is probably generated at browser startup, but the line media_device_id_salt_.setValue will not be executed because the file cache is already there. As a result, it took a lot of time to find the media_device_id_salt value initialized (mainly because it was supposed to be generated when passed into the constructor by pref_service, but indeed, in pref_service there are files cached to the value, but the code is not easy to find).
  3. Found this, tried to delete all chromium user files, tuned again, found media_device_ID_salt_.setValue, and generated a random value, And cache the “AppData Local Chromium User Data Default Preferences” file (electron in his own installation directory Preferences file, this file is a JSON format, This is the end of the search for device_id and group_id salt

Device_id and group_id salt are random numbers that are generated during the first Chromium initialization and written to the Preferences file, The user can obtain the value of media_devcie_id_salt by reading the Preferences file (JSON format)

Let’s continue enumerateDevices’ process and look for the device_id and group_id raw data and their hash methods

MediaDevicesManager::CheckPermissionsForEnumerateDevices

chromium/content/browser/renderer_host/media/media_devices_manager.cc:663

void MediaDevicesManager::CheckPermissionsForEnumerateDevices(
   int render_process_id,
   int render_frame_id,
   const BoolDeviceTypes& requested_types,
   bool request_video_input_capabilities,
   bool request_audio_input_capabilities,
   EnumerateDevicesCallback callback,
   MediaDeviceSaltAndOrigin salt_and_origin) {
 DCHECK_CURRENTLY_ON(BrowserThread::IO);
 permission_checker_->CheckPermissions(
     requested_types, render_process_id, render_frame_id,
     base::BindOnce(&MediaDevicesManager::OnPermissionsCheckDone,
                    weak_factory_.GetWeakPtr(), requested_types,
                    request_video_input_capabilities,
                    request_audio_input_capabilities, std::move(callback),
                    std::move(salt_and_origin)));
}

Copy the code

Then EnumerateDevices above, When he will return the results to call out GetMediaDeviceSaltAndOrigin MediaDevicesManager: : CheckPermissionsForEnumerateDevices, This function also calls the CheckPermissions function and returns the result to OnPermissionsCheckDone

MediaDevicesManager::OnPermissionsCheckDone

chromium/content/browser/renderer_host/media/media_devices_manager.cc:663

void MediaDevicesManager::OnPermissionsCheckDone(
   const MediaDevicesManager::BoolDeviceTypes& requested_types,
   bool request_video_input_capabilities,
   bool request_audio_input_capabilities,
   EnumerateDevicesCallback callback,
   MediaDeviceSaltAndOrigin salt_and_origin,
   const MediaDevicesManager::BoolDeviceTypes& has_permissions) {
 DCHECK_CURRENTLY_ON(BrowserThread::IO);
 / /...

 EnumerateDevices(
     internal_requested_types,
     base::BindOnce(&MediaDevicesManager::OnDevicesEnumerated,
                    weak_factory_.GetWeakPtr(), requested_types,
                    request_video_input_capabilities,
                    request_audio_input_capabilities, std::move(callback),
                    std::move(salt_and_origin), has_permissions));
}
Copy the code

Calling EnumerateDevices results in a callback to OnDevicesEnumerated

MediaDevicesManager::EnumerateDevices

chromium/content/browser/renderer_host/media/media_devices_manager.cc:405

void MediaDevicesManager::EnumerateDevices(
   const BoolDeviceTypes& requested_types,
   EnumerationCallback callback) {
 DCHECK_CURRENTLY_ON(BrowserThread::IO);
 StartMonitoring(a); requests_.emplace_back(requested_types, std::move(callback));
 bool all_results_cached = true;
 for (size_t i = 0;
      i < static_cast<size_t>(MediaDeviceType::NUM_MEDIA_DEVICE_TYPES); ++i) {
   if (requested_types[i] && cache_policies_[i] == CachePolicy::NO_CACHE) {
     all_results_cached = false;
     DoEnumerateDevices(static_cast<MediaDeviceType>(i)); }}if (all_results_cached)
   ProcessRequests(a); }Copy the code

Ignore some code that doesn’t care, which is that this function calls DoEnumerateDevies

MediaDevicesManager::DoEnumerateDevices

chromium/content/browser/renderer_host/media/media_devices_manager.cc:877

void MediaDevicesManager::DoEnumerateDevices(MediaDeviceType type) {
  / /...

 switch (type) {
   case MediaDeviceType::MEDIA_AUDIO_INPUT:
     EnumerateAudioDevices(true /* is_input */);
     break;
   case MediaDeviceType::MEDIA_VIDEO_INPUT:
     video_capture_manager_->EnumerateDevices(
         base::BindOnce(&MediaDevicesManager::VideoInputDevicesEnumerated,
                        weak_factory_.GetWeakPtr()));
     break;
   case MediaDeviceType::MEDIA_AUDIO_OUTPUT:
     EnumerateAudioDevices(false /* is_input */);
     break;
   default:
     NOTREACHED();
 }
}
Copy the code

Ignore don’t care about the code, this switch is called the video_capture_manager_ – > EnumerateDevices, then the results back to transferred to MediaDevicesManager: : VideoInputDevicesEnumerated

VideoCaptureManager

chromium/content/browser/renderer_host/media/video_capture_manager.cc

void VideoCaptureManager::EnumerateDevices( EnumerationCallback client_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  EmitLogMessage("VideoCaptureManager::EnumerateDevices".1);

  // Pass a timer for UMA histogram collection.
  video_capture_provider_->GetDeviceInfosAsync(media::BindToCurrentLoop(
      base::BindOnce(&VideoCaptureManager::OnDeviceInfosReceived, this,
                     base::ElapsedTimer(), std::move(client_callback))));
}

void VideoCaptureManager::OnDeviceInfosReceived(
    base::ElapsedTimer timer,
    EnumerationCallback client_callback,
    const std::vector<media::VideoCaptureDeviceInfo>& device_infos) {
  / /...

  // Walk the |devices_info_cache_| and produce a
  // media::VideoCaptureDeviceDescriptors for |client_callback|.
  media::VideoCaptureDeviceDescriptors devices;
  std::vector<std::tuple<media::VideoCaptureDeviceDescriptor,
                         media::VideoCaptureFormats>>
      descriptors_and_formats;
  for (const auto& it : devices_info_cache_) {
    devices.emplace_back(it.descriptor);
    descriptors_and_formats.emplace_back(it.descriptor, it.supported_formats);
    MediaInternals::GetInstance() - >UpdateVideoCaptureDeviceCapabilities(
        descriptors_and_formats);
  }

  std::move(client_callback).Run(devices);
}

Copy the code

GetDeviceInfosAsync is called inside EnumerateDevices and the result is called OnDeviceInfoReceicved, OnDeviceInfoReceicved function did the main thing is to rush GetDeviceInfosAsync get to VideoCaptureInfo converts VideoCaptureDeviceDescriptors inside

struct CAPTURE_EXPORT VideoCaptureDeviceInfo {
  VideoCaptureDeviceInfo(a);VideoCaptureDeviceInfo(VideoCaptureDeviceDescriptor descriptor);
  VideoCaptureDeviceInfo(const VideoCaptureDeviceInfo& other);
  ~VideoCaptureDeviceInfo(a); VideoCaptureDeviceInfo&operator= (const VideoCaptureDeviceInfo& other);

  VideoCaptureDeviceDescriptor descriptor;
  VideoCaptureFormats supported_formats;
};

struct CAPTURE_EXPORT VideoCaptureDeviceDescriptor {
 public:
  VideoCaptureDeviceDescriptor(a);VideoCaptureDeviceDescriptor(
      const std::string& display_name,
      const std::string& device_id,
      VideoCaptureApi capture_api = VideoCaptureApi::UNKNOWN,
      const VideoCaptureControlSupport& control_support =
          VideoCaptureControlSupport(),
      VideoCaptureTransportType transport_type =
          VideoCaptureTransportType::OTHER_TRANSPORT);
  VideoCaptureDeviceDescriptor(
      const std::string& display_name,
      const std::string& device_id,
      const std::string& model_id,
      VideoCaptureApi capture_api,
      const VideoCaptureControlSupport& control_support,
      VideoCaptureTransportType transport_type =
          VideoCaptureTransportType::OTHER_TRANSPORT,
      VideoFacingMode facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE);
 / /...

  std::string device_id;
  // A unique hardware identifier of the capture device.
  // It is of the form "[vid]:[pid]" when a USB device is detected, and empty
  // otherwise.
  std::string model_id;

  VideoFacingMode facing;

  VideoCaptureApi capture_api;
  VideoCaptureTransportType transport_type;

 private:
  std::string display_name_;  // Name that is intended for display in the UI
  VideoCaptureControlSupport control_support_;
};

Copy the code

Which contains VideoCaptureDeviceDescriptor VideoCaptureDeviceInfo, VideoCaptureDeviceDescriptor structure is the data we want, It has device_id, model_id, display_name, and so on

Device_id Original data conclusion

After source moddings, device_id is found to be device_path on Windows

The last line of OnDeviceInfosReceived STD :: Move (client_callback).run (devices); , it will be called to MediaDevicesManager: : VideoInputDevicesEnumerated function

MediaDevicesManager::VideoInputDevicesEnumerated

chromium/content/browser/renderer_host/media/media_devices_manager.cc:921

void MediaDevicesManager::VideoInputDevicesEnumerated(
    const media::VideoCaptureDeviceDescriptors& descriptors) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  blink::WebMediaDeviceInfoArray snapshot;
  for (const auto& descriptor : descriptors) {
    snapshot.emplace_back(descriptor);
  }
  DevicesEnumerated(MediaDeviceType::MEDIA_VIDEO_INPUT, snapshot);
}
Copy the code

This function is also very simple to convert VideoCaptureDeviceDescriptors to WebMediaDeviceInfoArray, then call the DevicesEnumerated

WebMediaDeviceInfoArray & WebMediaDeviceInfo

chromium/third_party/blink/public/common/mediastream/media_devices.h

using WebMediaDeviceInfoArray = std::vector<WebMediaDeviceInfo>;

struct BLINK_COMMON_EXPORT WebMediaDeviceInfo {
  WebMediaDeviceInfo(a);WebMediaDeviceInfo(const WebMediaDeviceInfo& other);
  WebMediaDeviceInfo(WebMediaDeviceInfo&& other);
  WebMediaDeviceInfo(
      const std::string& device_id,
      const std::string& label,
      const std::string& group_id,
      const media::VideoCaptureControlSupport& video_control_support =
          media::VideoCaptureControlSupport(),
      blink::mojom::FacingMode video_facing = blink::mojom::FacingMode::NONE);
  explicit WebMediaDeviceInfo(
      const media::VideoCaptureDeviceDescriptor& descriptor);

  std::string device_id;
  std::string label;
  std::string group_id;
  media::VideoCaptureControlSupport video_control_support;
  blink::mojom::FacingMode video_facing = blink::mojom::FacingMode::NONE;
};
Copy the code

We have found devie_id and group_id. This structure corresponds perfectly to the js returned data. Device_id to the original data is known as device_path. The above VideoInputDevicesEnumerated function will be called to the explicit WebMediaDeviceInfo (const media: : VideoCaptureDeviceDescriptor & descriptor); This constructor

WebMediaDeviceInfo::WebMediaDeviceInfo

chromium/third_party/blink/common/mediastream/media_devices.cc:29

WebMediaDeviceInfo::WebMediaDeviceInfo(
    const media::VideoCaptureDeviceDescriptor& descriptor)
    : device_id(descriptor.device_id),
      label(descriptor.GetNameAndModel()),
      video_control_support(descriptor.control_support()),
      video_facing(static_cast<blink::mojom::FacingMode>(descriptor.facing)) {}
Copy the code

You can see that group_id is still empty after the constructor is executed

MediaDevicesManager::ProcessRequests

chromium/content/browser/renderer_host/media/media_devices_manager.cc:1019

void MediaDevicesManager::ProcessRequests(a) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Populate the group ID field for video devices using a heuristic that looks
  // for device coincidences with audio input devices.
  // TODO(crbug.com/627793): Remove this once the video-capture subsystem
  // supports group IDs.
  if (has_seen_result_[static_cast<size_t>(
          MediaDeviceType::MEDIA_VIDEO_INPUT)]) {
    blink::WebMediaDeviceInfoArray video_devices =
        current_snapshot_[static_cast<size_t>(
            MediaDeviceType::MEDIA_VIDEO_INPUT)];
    for (auto& video_device_info : video_devices) {
      video_device_info.group_id =
          GuessVideoGroupID(current_snapshot_[static_cast<size_t>(
                                MediaDeviceType::MEDIA_AUDIO_INPUT)],
                            video_device_info);
    }
    UpdateSnapshot(MediaDeviceType::MEDIA_VIDEO_INPUT, video_devices,
                   false /* ignore_group_id */);
  }

  base::EraseIf(requests_, [this](EnumerationRequest& request) {
    if (IsEnumerationRequestReady(request)) {
      std::move(request.callback).Run(current_snapshot_);
      return true;
    }
    return false;
  });
}
Copy the code

As you can see from the comment, this is a function to fill the gourp ID. The details are described in the GuessVideoGroupID function, which is to match the audio device associated with the video. Then copy the audio device’s groupid.

MediaDevicesManager::OnDevicesEnumerated

chromium/content/browser/renderer_host/media/media_devices_manager.cc:699

void MediaDevicesManager::OnDevicesEnumerated(
    const MediaDevicesManager::BoolDeviceTypes& requested_types,
    bool request_video_input_capabilities,
    bool request_audio_input_capabilities,
    EnumerateDevicesCallback callback,
    const MediaDeviceSaltAndOrigin& salt_and_origin,
    const MediaDevicesManager::BoolDeviceTypes& has_permissions,
    const MediaDeviceEnumeration& enumeration) {
  / /...

  std::vector<blink::WebMediaDeviceInfoArray> translation(
      static_cast<size_t>(MediaDeviceType::NUM_MEDIA_DEVICE_TYPES));
  for (size_t i = 0;
       i < static_cast<size_t>(MediaDeviceType::NUM_MEDIA_DEVICE_TYPES); ++i) {
    if(! requested_types[i])continue;

    for (const auto& device_info : enumeration[i]) {
      if (base::FeatureList::IsEnabled( features::kEnumerateDevicesHideDeviceIDs) && ! has_permissions[i] && ! translation[i].empty())
        break;

      translation[i].push_back(TranslateMediaDeviceInfo( has_permissions[i], salt_and_origin, device_info)); }}/ /...
}
Copy the code

OnDevicesEnumerated function will MediaDevicesManager here: : OnPermissionsCheckDone EnumerateDevices inside after trigger, this function has the hash, we are looking for In TranslateMediaDeviceInfo

WebMediaDeviceInfo TranslateMediaDeviceInfo

chromium/content/browser/media/media_devices_util.cc:181

blink::WebMediaDeviceInfo TranslateMediaDeviceInfo(
    bool has_permission,
    const MediaDeviceSaltAndOrigin& salt_and_origin,
    const blink::WebMediaDeviceInfo& device_info) {
  return blink::WebMediaDeviceInfo(
      !base::FeatureList::IsEnabled(features::kEnumerateDevicesHideDeviceIDs) ||
              has_permission
          ? GetHMACForMediaDeviceID(salt_and_origin.device_id_salt,
                                    salt_and_origin.origin,
                                    device_info.device_id)
          : std::string(),
      has_permission ? device_info.label : std::string(),
      device_info.group_id.empty()? std::string()
          : GetHMACForMediaDeviceID(salt_and_origin.group_id_salt,
                                    salt_and_origin.origin,
                                    device_info.group_id),
      has_permission ? device_info.video_control_support
                     : media::VideoCaptureControlSupport(),
      has_permission ? device_info.video_facing
                     : blink::mojom::FacingMode::NONE);
}
Copy the code

So you can see we’re back in blink, TranslateMediaDeviceInfo and all we care about is a function GetHMACForMediaDeviceID that’s going to use HMAC to generate a hash value

MediaStreamManager::GetHMACForMediaDeviceID

chromium/content/browser/renderer_host/media/media_stream_manager.cc:2561

std::string MediaStreamManager::GetHMACForMediaDeviceID(
    const std::string& salt,
    const url::Origin& security_origin,
    const std::string& raw_unique_id) {
  // TODO(crbug.com/1215532): DCHECKs are disabled during automated testing on
  // CrOS and this check failed when tested on an experimental builder. Revert
  // https://crrev.com/c/2932244 to enable it. See go/chrome-dcheck-on-cros
  // or http://crbug.com/1113456 for more details.
#if! defined(OS_CHROMEOS)
  DCHECK(! raw_unique_id.empty());
#endif
  if (raw_unique_id == media::AudioDeviceDescription::kDefaultDeviceId ||
      raw_unique_id == media::AudioDeviceDescription::kCommunicationsDeviceId) {
    return raw_unique_id;
  }

  crypto::HMAC hmac(crypto::HMAC::SHA256);
  const size_t digest_length = hmac.DigestLength(a);std::vector<uint8_t> digest(digest_length);
  bool result = hmac.Init(security_origin.Serialize()) &&
                hmac.Sign(raw_unique_id + salt, &digest[0], digest.size());
  DCHECK(result);
  return base::ToLowerASCII(base::HexEncode(&digest[0], digest.size()));
}
Copy the code

It can be seen from here that device_id is used to add salt and then HMAC SHA256, but there is still a problem that it uses HMAC and has a key, so this key must be analyzed, otherwise we cannot generate the same device ID as it. This means that device_id can also change depending on the key, without changing the salt. Is this security_origin by analyzing from the above we GetMediaDeviceSaltAndOrigin get inside, get of is the frame of the current browser last commit origin

Origin::Serialize

chromium/url/origin.cc

std::string Origin::Serialize(a) const {
  if (opaque())
    return "null";

  if (scheme() == kFileScheme)
    return "file://";

  return tuple_.Serialize(a); }Copy the code
  1. If the origin of the frame is opaque, I do not understand the opaque, I do not know what will happen. The code is that the origin of some websites is not public, so I return empty
  2. If sheme is file, return “file://”
  3. The third is the normal situation, it will be called to SchemeHostPort: : SerializeInternal function

SchemeHostPort::SerializeInternal

chromium/url/scheme_host_port.cc

std::string SchemeHostPort::SerializeInternal(url::Parsed* parsed) const {
  std::string result;
  if (!IsValid())
    return result;

  // Reserve enough space for the "normal" case of scheme://host/.
  result.reserve(scheme_.size() + host_.size() + 4);

  if(! scheme_.empty()) {
    parsed->scheme = Component(0, scheme_.length());
    result.append(scheme_);
  }

  result.append(kStandardSchemeSeparator);

  if(! host_.empty()) {
    parsed->host = Component(result.length(), host_.length());
    result.append(host_);
  }

  // Omit the port component if the port matches with the default port
  // defined for the scheme, if any.
  int default_port = DefaultPortForScheme(scheme_.data(),
                                          static_cast<int>(scheme_.length()));
  if (default_port == PORT_UNSPECIFIED)
    return result;
  if(port_ ! = default_port) { result.push_back(':');
    std::string port(base::NumberToString(port_));
    parsed->port = Component(result.length(), port.length());
    result.append(std::move(port));
  }

  return result;
}
Copy the code

As you can clearly see from the code, it takes three things together: Scheme, host, and port, but port is special. If the port number of scheme matches the default port, it will ignore this port. For example, if your site is port 80, it will ignore this port

conclusion

device_id : The source is the device Path on Windows and the encryption is HMAC SHA256. Salt is a random value that can be obtained by reading the Preferences file (JSON format). The key in HMAC SHA256 is the root domain address of the last Frame Origin you accessed

group_id : The video group_id is copied from audio’s gourp ID. The specific source of audio is not analyzed. Since device_id can solve the problem, the encryption mode of group_id is exactly the same as that of device ID, but the salt is frame_salt. It changes depending on the frame